Merge: annotations: introduce `example` annotation
authorJean Privat <jean@pryen.org>
Mon, 4 Dec 2017 14:07:40 +0000 (09:07 -0500)
committerJean Privat <jean@pryen.org>
Mon, 4 Dec 2017 14:07:40 +0000 (09:07 -0500)
## Motivations

When looking at the nit libraries, it's not rare to find external files used only to store examples that cannot be expressed as docunit.

The `example` annotation aims at tag pieces of code that may be considered as examples and thus be:
1. ignored be the compilation tools
2. used by documentation tools (and testing tools)

## Why a new annotation?

Rather than the `examples/` folder convention?

1. Examples can now be included directly in the module code if needed
2. Makes it easier to separate real code example from helper code and example tests
3. More flexibility in project organization and namming
4. To be coherent with the `test` annotation

## How to use it?

Simply tag the MEntity with `example`:

~~~nit
module my_example_module is example
~~~

Can also be applied on classes and methods:

~~~nit
module my_module # is not example

# Abstract class A, we should provide an example of subclass...
# So let's see `ExampleA`.
abstract class A
end

# This class provides an example of how to sublass A.
class ExampleA is example
   super A
end

class C
    # This method provides an example of how to instantiate `A` sublcasses
    fun example_init_a is example do
        var a: A = new ExampleA
    end
end
~~~

## Incoming work

This PR only introduces the annotation and applies it to the existing source code.

Next PR will make tools like nitweb, nitdoc and nitx use it (via commands #2580).

Pull-Request: #2581
Reviewed-by: Alexandre Blondin Massé <alexandre.blondin.masse@gmail.com>
Reviewed-by: Jean Privat <jean@pryen.org>

40 files changed:
contrib/asteronits/src/asteronits.nit
lib/android/README.md
lib/android/assets_and_resources.nit
lib/android/native_app_glue.nit
lib/android/nit_activity.nit
lib/egl.nit
lib/gamnit/cameras.nit
lib/gamnit/depth/depth.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/particles.nit
lib/gamnit/depth/shadow.nit
lib/gamnit/display_android.nit
lib/gamnit/display_linux.nit
lib/gamnit/dynamic_resolution.nit
lib/gamnit/flat/flat_core.nit
lib/gamnit/limit_fps.nit
lib/gamnit/programs.nit
lib/gamnit/textures.nit
lib/glesv2/glesv2.nit
lib/matrix/matrix.nit
lib/matrix/projection.nit
lib/performance_analysis.nit
lib/realtime.nit
share/man/nitlight.md
src/doc/commands/commands_model.nit
src/doc/doc_down.nit
src/examples/nitlight_as_a_service.nit
src/highlight.nit
src/htmlight.nit [new file with mode: 0644]
src/nitlight.nit
src/platform/android.nit
src/test_highlight.nit
src/web/api_docdown.nit
src/web/api_light.nit
src/web/api_model.nit
tests/nitlight.args
tests/sav/nitlight_args4.res [new file with mode: 0644]
tests/sav/test_jvm.res
tests/tests.sh

index cc9f6a8..628fc69 100644 (file)
@@ -70,6 +70,8 @@ redef class App
 
                # Move the camera to show all the world world in the screen range
                world_camera.reset_height(world.half_height * 2.0)
+
+               ui_camera.reset_height 720.0
        end
 
        # Main spritesheet with ships, asteroids and beams
index 457f426..ffd961c 100644 (file)
@@ -102,6 +102,20 @@ Importing `android::landscape` or `android::portrait` locks the generated
 application in the specified orientation. This can be useful for games and
 other multimedia applications.
 
+## Resources and application icon
+
+Resources specific to the Android platform should be placed in an `android/` folder at the root of the project.
+The folder should adopt the structure of a normal Android project, e.g., a custom XML resource file can be placed
+at `android/res/values/color.xml` to be compiled with the Android application.
+
+The application icon should also be placed in the `android/` folder.
+Place the classic bitmap version at `android/res/mipmap-hdpi/ic_launcher.png` (and others),
+and the adaptive version at `android/res/mipmap-anydpi-v26/ic_launcher.xml`.
+The Nit compiler detects these files and uses them as the application icon.
+
+Additional `android/` folders may be placed next to more specific Nit modules to change the Android resources
+for application variants. The more specific resources will have priority over the project level `android/` files.
+
 # Compilation modes
 
 There are two compilation modes for the Android platform, debug and release.
index 40dace2..930bc17 100644 (file)
@@ -327,6 +327,10 @@ private extern class NativeBitmap in "Java" `{ android.graphics.Bitmap `}
                return self.hasAlpha();
        `}
 
+       fun recycle in "Java" `{
+               self.recycle();
+       `}
+
        # HACK for bug #845
        redef fun new_global_ref import sys, Sys.jni_env `{
                Sys sys = NativeBitmap_sys(self);
index 2a8f895..1769e0c 100644 (file)
@@ -195,7 +195,7 @@ redef class App
        # Notification from the Android framework, the system is running low on memory
        #
        # Try to reduce your memory use.
-       fun low_memory do end
+       fun low_memory do force_garbage_collection
 
        # Notification from the Android framework, the current device configuration has changed
        fun config_changed do end
index dac5a78..f084958 100644 (file)
@@ -287,7 +287,7 @@ class Activity
        # Notification from Android, the system is running low on memory
        #
        # Try to reduce your memory use.
-       fun on_low_memory do end
+       fun on_low_memory do force_garbage_collection
 
        # Notification from Android, the current window of the activity has lost or gained focus
        fun on_window_focus_changed(has_focus: Bool) do end
index a39b1c6..5d8f4f9 100644 (file)
@@ -216,6 +216,8 @@ class EGLConfigAttribs
        fun red_size: Int do return display.config_attrib(config, 0x3024)
        fun depth_size: Int do return display.config_attrib(config, 0x3025)
        fun stencil_size: Int do return display.config_attrib(config, 0x3026)
+       fun samples: Int do return display.config_attrib(config, 0x3031)
+       fun sample_buffers: Int do return display.config_attrib(config, 0x3032)
 
        fun native_visual_id: Int do return display.config_attrib(config, 0x302E)
        fun native_visual_type: Int do return display.config_attrib(config, 0x302F)
@@ -427,7 +429,8 @@ class EGLConfigChooser
        fun alpha_size=(size: Int) do insert_attrib_with_val(0x3021, size)
        fun depth_size=(size: Int) do insert_attrib_with_val(0x3025, size)
        fun stencil_size=(size: Int) do insert_attrib_with_val(0x3026, size)
-       fun sample_buffers=(size: Int) do insert_attrib_with_val(0x3031, size)
+       fun samples=(count: Int) do insert_attrib_with_val(0x3031, count)
+       fun sample_buffers=(size: Int) do insert_attrib_with_val(0x3032, size)
 
        fun caveat=(caveat: EGLConfigCaveat) do insert_attrib_with_val(0x3050, caveat.to_i)
 
index c83ace3..33aac41 100644 (file)
@@ -245,10 +245,10 @@ class UICamera
        var bottom: IPoint3d[Float] = new CameraAnchor(self, 0.5, -1.0)
 
        # Center of the left border of the screen, at z = 0
-       var left: IPoint3d[Float] = new CameraAnchor(self, 0.0, -1.0)
+       var left: IPoint3d[Float] = new CameraAnchor(self, 0.0, -0.5)
 
        # Center of the right border of the screen, at z = 0
-       var right: IPoint3d[Float] = new CameraAnchor(self, 1.0, -1.0)
+       var right: IPoint3d[Float] = new CameraAnchor(self, 1.0, -0.5)
 
        # Top left corner of the screen, at z = 0
        var top_left: IPoint3d[Float] = new CameraAnchor(self, 0.0, 0.0)
index 2e4fc82..a7864f9 100644 (file)
@@ -42,7 +42,7 @@ redef class App
                glCullFace gl_BACK
 
                # Prepare programs
-               var programs = [versatile_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
+               var programs = [blinn_phong_program, normals_program, explosion_program, smoke_program, static_program, selection_program: GamnitProgram]
                for program in programs do
                        program.compile_and_link
                        var gamnit_error = program.error
@@ -87,11 +87,12 @@ redef class App
                glDepthMask true
                perfs["gamnit depth particles"].add frame_core_depth_clock.lapse
 
+               # Stop using the dynamic resolution before drawing UI sprites
+               frame_core_dynamic_resolution_after display
+
                frame_core_ui_sprites display
                perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse
 
-               frame_core_dynamic_resolution_after display
-
                # Debug, show the light point of view
                #frame_core_shadow_debug display
        end
index 0e45e3d..e7e42de 100644 (file)
@@ -185,7 +185,10 @@ end
 # ~~~
 class Mesh
 
-       # Vertices coordinates
+       # Number for vertices
+       fun n_vertices: Int do return vertices.length / 3
+
+       # Vertices relative coordinates, 3 floats per vertex
        var vertices = new Array[Float] is lazy, writable
 
        # Indices to draw triangles with `glDrawElements`
@@ -195,10 +198,10 @@ class Mesh
 
        private var indices_c = new CUInt16Array.from(indices) is lazy, writable
 
-       # Normals on each vertex
+       # Normals, 3 floats per vertex
        var normals = new Array[Float] is lazy, writable
 
-       # Coordinates on the texture per vertex
+       # Coordinates on the texture, 2 floats per vertex
        var texture_coords = new Array[Float] is lazy, writable
 
        # `GLDrawMode` used to display this mesh, defaults to `gl_TRIANGLES`
index 8de6f9d..6674c04 100644 (file)
@@ -49,16 +49,20 @@ class SmoothMaterial
 
        redef fun draw(actor, model, camera)
        do
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
                program.mvp.uniform camera.mvp_matrix
 
                var mesh = model.mesh
 
                # Actor specs
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
+
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.alpha.uniform actor.alpha
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                # From mesh
                program.coord.array_enabled = true
@@ -77,15 +81,14 @@ class SmoothMaterial
                program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
 
                # Colors from the material
-               var a = actor.alpha
-               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
-                                             ambient_color[2]*a, ambient_color[3]*a)
-               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
-                                             diffuse_color[2]*a, diffuse_color[3]*a)
-               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
-                                              specular_color[2]*a, specular_color[3]*a)
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
 
-               setup_lights(actor, model, camera, program)
+               setup_lights(camera, program)
 
                # Execute draw
                if mesh.indices.is_empty then
@@ -97,7 +100,7 @@ class SmoothMaterial
                assert glGetError == gl_NO_ERROR
        end
 
-       private fun setup_lights(actor: Actor, model: LeafModel, camera: Camera, program: BlinnPhongProgram)
+       private fun setup_lights(camera: Camera, program: BlinnPhongProgram)
        do
                # TODO use a list of lights
 
@@ -158,7 +161,7 @@ class TexturedMaterial
        do
                var mesh = model.mesh
 
-               var program = app.versatile_program
+               var program = app.blinn_phong_program
                program.use
 
                # One of the textures used, if any
@@ -208,9 +211,13 @@ class TexturedMaterial
                        program.use_map_bump.uniform false
                end
 
+               glDisableVertexAttribArray program.translation.location
+               glDisableVertexAttribArray program.scale.location
+
                program.mvp.uniform camera.mvp_matrix
                program.translation.uniform(actor.center.x, actor.center.y, actor.center.z, 0.0)
                program.scale.uniform actor.scale
+               program.alpha.uniform actor.alpha
 
                # If using a texture, set `texture_coords`
                program.tex_coord.array_enabled = sample_used_texture != null
@@ -239,21 +246,20 @@ class TexturedMaterial
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
 
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
-               var a = actor.alpha
-               program.ambient_color.uniform(ambient_color[0]*a, ambient_color[1]*a,
-                                             ambient_color[2]*a, ambient_color[3]*a)
-               program.diffuse_color.uniform(diffuse_color[0]*a, diffuse_color[1]*a,
-                                             diffuse_color[2]*a, diffuse_color[3]*a)
-               program.specular_color.uniform(specular_color[0]*a, specular_color[1]*a,
-                                              specular_color[2]*a, specular_color[3]*a)
+               program.ambient_color.uniform(ambient_color[0], ambient_color[1],
+                                             ambient_color[2], ambient_color[3])
+               program.diffuse_color.uniform(diffuse_color[0], diffuse_color[1],
+                                             diffuse_color[2], diffuse_color[3])
+               program.specular_color.uniform(specular_color[0], specular_color[1],
+                                              specular_color[2], specular_color[3])
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
 
                # Light
-               setup_lights(actor, model, camera, program)
+               setup_lights(camera, program)
 
                # Camera
                program.camera.uniform(camera.position.x, camera.position.y, camera.position.z)
@@ -292,7 +298,7 @@ class NormalsMaterial
                program.coord.array_enabled = true
                program.coord.array(mesh.vertices, 3)
 
-               program.rotation.uniform new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
+               program.rotation = new Matrix.gamnit_euler_rotation(actor.pitch, actor.yaw, actor.roll)
 
                program.normal.array_enabled = true
                program.normal.array(mesh.normals, 3)
@@ -314,10 +320,12 @@ class BlinnPhongProgram
                attribute vec4 coord;
 
                // Vertex translation
-               uniform vec4 translation;
+               attribute vec4 translation;
 
                // Vertex scaling
-               uniform float scale;
+               attribute float scale;
+
+               attribute float alpha;
 
                // Vertex coordinates on textures
                attribute vec2 tex_coord;
@@ -328,7 +336,16 @@ class BlinnPhongProgram
                // Camera model view projection matrix
                uniform mat4 mvp;
 
-               uniform mat4 rotation;
+               // Actor rotation
+               attribute vec4 rotation_row0;
+               attribute vec4 rotation_row1;
+               attribute vec4 rotation_row2;
+               attribute vec4 rotation_row3;
+
+               mat4 rotation()
+               {
+                       return mat4(rotation_row0, rotation_row1, rotation_row2, rotation_row3);
+               }
 
                // Lights config
                uniform lowp int light_kind;
@@ -344,9 +361,11 @@ class BlinnPhongProgram
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
                varying vec4 v_depth_pos;
+               varying float v_alpha;
 
                void main()
                {
+                       mat4 rotation = rotation();
                        vec4 pos = (vec4(coord.xyz * scale, 1.0) * rotation + translation);
                        gl_Position = pos * mvp;
                        v_depth_pos = (pos * light_mvp) * 0.5 + 0.5;
@@ -365,6 +384,8 @@ class BlinnPhongProgram
                                // Point light (and others?)
                                v_to_light = normalize(vec4(light_center, 1.0) - pos);
                        }
+
+                       v_alpha = alpha;
                }
                """ @ glsl_vertex_shader
 
@@ -377,6 +398,7 @@ class BlinnPhongProgram
                varying vec4 v_to_light;
                varying vec4 v_to_camera;
                varying vec4 v_depth_pos;
+               varying float v_alpha;
 
                // Colors
                uniform vec4 ambient_color;
@@ -407,13 +429,13 @@ class BlinnPhongProgram
                uniform lowp int light_kind;
                uniform bool use_shadows;
                uniform sampler2D depth_texture;
-               uniform float depth_texture_size;
-               uniform int depth_texture_taps;
+               uniform float depth_size;
+               uniform int depth_taps;
 
                // Shadow effect on the diffuse colors of the fragment at offset `x, y`
                float shadow_lookup(vec2 depth_coord, float x, float y) {
                        float tap_width = 1.0;
-                       float pixel_size = tap_width/depth_texture_size;
+                       float pixel_size = tap_width/depth_size;
 
                        vec2 offset = vec2(x * pixel_size * v_depth_pos.w,
                                           y * pixel_size * v_depth_pos.w);
@@ -448,7 +470,7 @@ class BlinnPhongProgram
 
                        vec2 depth_coord = v_depth_pos.xy/v_depth_pos.w;
 
-                       float taps = float(depth_texture_taps);
+                       float taps = float(depth_taps);
                        float tap_step = 2.00/taps;
                        float sum = 0.0;
                        for (float x = -1.0; x <= 0.99; x += tap_step)
@@ -467,13 +489,13 @@ class BlinnPhongProgram
                        }
 
                        // Ambient light
-                       vec4 ambient = ambient_color;
+                       vec4 ambient = ambient_color * v_alpha;
                        if (use_map_ambient) ambient *= texture2D(map_ambient, v_tex_coord);
 
                        if (light_kind == 0) {
                                // No light, show diffuse and ambient
 
-                               vec4 diffuse = diffuse_color;
+                               vec4 diffuse = diffuse_color * v_alpha;
                                if (use_map_diffuse) diffuse *= texture2D(map_diffuse, v_tex_coord);
 
                                gl_FragColor = ambient + diffuse;
@@ -499,7 +521,7 @@ class BlinnPhongProgram
                                        diffuse *= shadow();
                                }
 
-                               vec4 specular = s * specular_color;
+                               vec4 specular = s * specular_color * v_alpha;
                                if (use_map_specular) specular *= texture2D(map_specular, v_tex_coord).x;
 
                                gl_FragColor = ambient + diffuse + specular;
@@ -569,22 +591,49 @@ class BlinnPhongProgram
        var depth_texture = uniforms["depth_texture"].as(UniformSampler2D) is lazy
 
        # Size, in pixels, of `depth_texture`
-       var depth_texture_size = uniforms["depth_texture_size"].as(UniformFloat) is lazy
+       var depth_texture_size = uniforms["depth_size"].as(UniformFloat) is lazy
 
        # Times to tap the `depth_texture`, square root (set to 3 for a total of 9 taps)
-       var depth_texture_taps = uniforms["depth_texture_taps"].as(UniformInt) is lazy
+       var depth_texture_taps = uniforms["depth_taps"].as(UniformInt) is lazy
 
        # Camera position
        var camera = uniforms["camera"].as(UniformVec3) is lazy
 
        # Translation applied to each vertex
-       var translation = uniforms["translation"].as(UniformVec4) is lazy
+       var translation = attributes["translation"].as(AttributeVec4) is lazy # TODO attribute
 
-       # Rotation matrix
-       var rotation = uniforms["rotation"].as(UniformMat4) is lazy
+       # Set `mat` at the uniform rotation matrix
+       fun rotation=(mat: Matrix)
+       do
+               var i = 0
+               for r in [rotation_row0, rotation_row1, rotation_row2, rotation_row3] do
+                       if r.is_active then
+                               glDisableVertexAttribArray r.location
+                               r.uniform(mat[0, i], mat[1, i], mat[2, i], mat[3, i])
+                       end
+                       i += 1
+               end
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print_error gl_error
+       end
+
+       # Rotation matrix, row0
+       var rotation_row0 = attributes["rotation_row0"].as(AttributeVec4) is lazy
+
+       # Rotation matrix, row 1
+       var rotation_row1 = attributes["rotation_row1"].as(AttributeVec4) is lazy
+
+       # Rotation matrix, row 2
+       var rotation_row2 = attributes["rotation_row2"].as(AttributeVec4) is lazy
+
+       # Rotation matrix, row 3
+       var rotation_row3 = attributes["rotation_row3"].as(AttributeVec4) is lazy
+
+       # Scaling per vertex
+       var scale = attributes["scale"].as(AttributeFloat) is lazy
 
        # Scaling per vertex
-       var scale = uniforms["scale"].as(UniformFloat) is lazy
+       var alpha = attributes["alpha"].as(AttributeFloat) is lazy
 
        # Camera model view projection matrix
        var mvp = uniforms["mvp"].as(UniformMat4) is lazy
@@ -610,7 +659,7 @@ class NormalProgram
 end
 
 redef class App
-       private var versatile_program = new BlinnPhongProgram is lazy
+       private var blinn_phong_program = new BlinnPhongProgram is lazy
 
        private var normals_program = new NormalProgram is lazy
 end
index bfe3392..557e694 100644 (file)
@@ -89,6 +89,16 @@ class ParticleSystem
        # Time-to-live of each particle
        private var ttls = new Array[Float]
 
+       # Clear all particles
+       fun clear
+       do
+               centers.clear
+               ots.clear
+               scales.clear
+               ttls.clear
+               total_particles = 0
+       end
+
        # Add a particle at `center` with `scale`, living for `ttl` from `time_offset`
        #
        # `time_offset` is added to the creation time, it can be used to delay the
index d65a3cb..75178f5 100644 (file)
@@ -84,8 +84,6 @@ redef class App
                var light = app.light
                if not light isa LightCastingShadows then return
 
-               perf_clock_shadow.lapse
-
                # Make sure there's no errors pending
                assert glGetError == gl_NO_ERROR
 
@@ -116,8 +114,6 @@ redef class App
                # Take down, bring back default values
                glBindFramebuffer(gl_FRAMEBUFFER, shadow_context.screen_framebuffer)
                glColorMask(true, true, true, true)
-
-               perfs["gamnit shadows prep"].add perf_clock_shadow.lapse
        end
 
        # ---
index b14cb33..6b069d3 100644 (file)
@@ -74,6 +74,8 @@ redef class TextureAsset
                var pixels = buf.native_array
 
                load_from_pixels(pixels, bmp.width, bmp.height, gl_RGBA)
+               buf.destroy
+               bmp.recycle
 
                jni_env.pop_local_frame
        end
index dcfd963..e9af008 100644 (file)
@@ -139,5 +139,7 @@ redef class TextureAsset
                var pixels = surface.pixels
 
                load_from_pixels(pixels, surface.w, surface.h, format)
+
+               surface.free
        end
 end
index 7399dd9..0219db4 100644 (file)
@@ -42,7 +42,7 @@ redef class App
        #
        # This value is applied to both X and Y, so it has an exponential effect on
        # the number of pixels.
-       var dynamic_resolution_ratio = 1.0
+       var dynamic_resolution_ratio = 1.0 is writable
 
        # Minimum dynamic screen resolution
        var min_dynamic_resolution_ratio = 0.0125 is writable
index 2ab18f6..22ea7b3 100644 (file)
@@ -80,27 +80,27 @@ class Sprite
        var texture: Texture is writable(texture_direct=)
 
        # Texture drawn to screen
-       fun texture=(value: Texture)
+       fun texture=(texture: Texture)
        do
-               if isset _texture and value != texture then
+               if isset _texture and texture != self.texture then
                        needs_update
-                       if value.root != texture.root then needs_remap
+                       if texture.root != self.texture.root then needs_remap
                end
-               texture_direct = value
+               texture_direct = texture
        end
 
        # Center position of this sprite in world coordinates
        var center: Point3d[Float] is writable(center_direct=), noautoinit
 
        # Center position of this sprite in world coordinates
-       fun center=(value: Point3d[Float]) is autoinit do
-               if isset _center and value != center then
+       fun center=(center: Point3d[Float]) is autoinit do
+               if isset _center and center != self.center then
                        needs_update
-                       center.sprites_remove self
+                       self.center.sprites_remove self
                end
 
-               value.sprites_add self
-               center_direct = value
+               center.sprites_add self
+               center_direct = center
        end
 
        # Last animation set with `animate`
@@ -1753,10 +1753,11 @@ end
 redef class GLfloatArray
        private fun fill_from_matrix(matrix: Matrix, dst_offset: nullable Int)
        do
-               dst_offset = dst_offset or else 0
+               dst_offset = dst_offset or else add_index
                var mat_len = matrix.width*matrix.height
                assert length >= mat_len + dst_offset
                native_array.fill_from_matrix_native(matrix.items, dst_offset, mat_len)
+               add_index += mat_len
        end
 end
 
index 896f07f..ed73f5f 100644 (file)
@@ -25,7 +25,7 @@ redef class App
        # Zero (or a negative value) means no limit.
        #
        # Applications can modify this value even during the main-loop.
-       var maximum_fps = 60.0 is writable
+       var maximum_fps = 0.0 is writable
 
        # Current frame-rate
        #
index fc02530..9df20cd 100644 (file)
@@ -93,7 +93,7 @@ class Attribute
 
                var native = native_float_array
                if native == null or array.length > native.length then
-                       if native != null then native.destroy
+                       if native != null then native.finalize
                        native = new GLfloatArray.from(array)
                        self.native_float_array = native
                else
@@ -566,8 +566,8 @@ redef extern class NativeGLfloatArray
        # Overwrite this matrix with the identity matrix
        fun set_identity
        do
-               for i in 4.times do
-                       for j in 4.times do
+               for i in [0..4[ do
+                       for j in [0..4[ do
                                matrix_set(i, j, if i == j then 1.0 else 0.0)
                        end
                end
@@ -584,8 +584,8 @@ redef class Matrix
        # Copy content of this matrix to a `NativeGLfloatArray`
        fun fill_native(native: NativeGLfloatArray)
        do
-               for i in width.times do
-                       for j in height.times do
+               for i in [0..width[ do
+                       for j in [0..height[ do
                                native.matrix_set(i, j, self[i, j])
                        end
                end
index 60cec5a..4383c2b 100644 (file)
@@ -154,10 +154,9 @@ class CustomTexture
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
        #
-       # Require: `not loaded and x < width.to_i and y < height.to_i`
+       # Require: `x < width.to_i and y < height.to_i`
        fun []=(x, y: Int, color: Array[Float])
        do
-               assert not loaded else print_error "{class_name}::[]= already loaded"
                assert x < width.to_i and y < height.to_i else print_error "{class_name}::[] out of bounds"
 
                # Simple conversion from [0.0..1.0] to [0..255]
@@ -166,18 +165,16 @@ class CustomTexture
 
                var offset = 4*(x + y*width.to_i)
                for i in [0..4[ do cpixels[offset+i] = bytes[i]
+
+               loaded = false
        end
 
        # Overwrite all pixels with `color`, return `self`
        #
        # The argument `color` should be an array of up to 4 floats (RGBA).
        # If `color` has less than 4 items, the missing items are replaced by 1.0.
-       #
-       # Require: `not loaded`
        fun fill(color: Array[Float]): SELF
        do
-               assert not loaded else print_error "{class_name}::fill already loaded"
-
                # Simple conversion from [0.0..1.0] to [0..255]
                var bytes = [for c in color do (c*255.0).round.to_i.clamp(0, 255).to_bytes.last]
                while bytes.length < 4 do bytes.add 255u8
@@ -190,12 +187,20 @@ class CustomTexture
                        end
                end
 
+               loaded = false
                return self
        end
 
        redef fun load(force)
        do
-               if loaded then return
+               force = force or else false
+               if loaded and not force then return
+
+               if force and glIsTexture(gl_texture) then
+                       # Was already loaded, free the previous GL name
+                       glDeleteTextures([gl_texture])
+               end
+               gl_texture = -1
 
                # Round down the desired dimension
                var width = width.to_i
@@ -205,7 +210,6 @@ class CustomTexture
 
                load_from_pixels(cpixels.native_array, width, height, gl_RGBA)
 
-               cpixels.destroy
                loaded = true
        end
 end
@@ -338,7 +342,7 @@ abstract class Subtexture
        # Parent texture, from which this texture was created
        var parent: Texture
 
-       redef var root = parent.root is lateinit
+       redef fun root do return parent.root
 
        redef fun load(force) do root.load(force)
 end
@@ -392,7 +396,7 @@ class TextureSet
 end
 
 redef class Pointer
-       # Multiply RBG values by their alpha value
+       # Multiply RGB values by their alpha value
        private fun premultiply_alpha(width, height: Int) `{
                uint8_t *bytes = (uint8_t *)self;
                int x, y, i = 0;
index c4ac331..b8e0bcb 100644 (file)
@@ -434,12 +434,27 @@ fun glUniform4f(index: Int, x, y, z, w: Float) `{ glUniform4f(index, x, y, z, w)
 
 # Low level array of `Float`
 class GLfloatArray
-       super CArray[Float]
-       redef type NATIVE: NativeGLfloatArray
+       super FinalizableOnce
 
-       redef init(length)
+       var length: Int
+
+       var native_array = new NativeGLfloatArray(length) is lateinit
+
+       fun [](index: Int): Float do return native_array[index]
+
+       fun []=(index: Int, val: Float) do native_array[index] = val
+
+       var add_index = 0
+
+       fun reset_add do add_index = 0
+
+       # Require: `add_index < length`
+       fun add(value: Float)
        do
-               native_array = new NativeGLfloatArray(length)
+               var index = add_index
+               assert index < length
+               native_array[index] = value
+               self.add_index = index + 1
        end
 
        # Create with the content of `array`
@@ -458,26 +473,27 @@ class GLfloatArray
        # Require: `length >= array.length + dst_offset or else 0`
        fun fill_from(array: Array[Float], dst_offset: nullable Int)
        do
-               dst_offset = dst_offset or else 0
+               dst_offset = dst_offset or else add_index
 
                assert length >= array.length + dst_offset
                for k in [0..array.length[ do
                        self[dst_offset+k] = array[k]
                end
        end
+
+       redef fun finalize_once do native_array.free
 end
 
 # An array of `GLfloat` in C (`GLfloat*`)
 extern class NativeGLfloatArray `{ GLfloat* `}
-       super NativeCArray
-       redef type E: Float
 
        new(size: Int) `{ return calloc(size, sizeof(GLfloat)); `}
 
-       redef fun [](index) `{ return self[index]; `}
-       redef fun []=(index, val) `{ self[index] = val; `}
+       fun [](index: Int): Float `{ return self[index]; `}
+
+       fun []=(index: Int, val: Float) `{ self[index] = val; `}
 
-       redef fun +(offset) `{ return self + offset; `}
+       fun +(offset: Int): NativeGLfloatArray `{ return self + offset; `}
 end
 
 # General type for OpenGL enumerations
index 5d9a89d..0c3acac 100644 (file)
@@ -52,8 +52,8 @@ class Matrix
 
                for j in height.times do assert items[j].length == width
 
-               for j in height.times do
-                       for i in width.times do
+               for j in [0..height[ do
+                       for i in [0..width[ do
                                self[j, i] = items[j][i]
                        end
                end
@@ -123,8 +123,8 @@ class Matrix
                assert size >= 0
 
                var matrix = new Matrix(size, size)
-               for i in size.times do
-                       for j in size.times do
+               for i in [0..size[ do
+                       for j in [0..size[ do
                                matrix[j, i] = if i == j then 1.0 else 0.0
                        end
                end
@@ -213,13 +213,7 @@ class Matrix
                assert self.width == other.height
 
                var out = new Matrix(other.width, self.height)
-               for j in self.height.times do
-                       for i in other.width.times do
-                               var sum = items[0].zero
-                               for k in self.width.times do sum += self[j, k] * other[k, i]
-                               out[j, i] = sum
-                       end
-               end
+               out.items.mul(items, other.items, self.width, self.height, other.width)
                return out
        end
 
@@ -335,4 +329,14 @@ private extern class NativeDoubleArray `{ double* `}
                        r = r * 3 / 2 + (long)(i*1024.0);
                return r;
        `}
+
+       fun mul(a, b: NativeDoubleArray, a_width, a_height, b_width: Int) `{
+               int i, j, k;
+               for (j = 0; j < a_height; j ++)
+                       for (i = 0; i < b_width; i ++) {
+                               float sum = 0.0;
+                               for (k = 0; k < a_width; k ++) sum += a[j*a_width + k] * b[k*b_width + i];
+                               self[j*b_width + i] = sum;
+                       }
+       `}
 end
index a9bf8b8..d42fa2a 100644 (file)
@@ -159,18 +159,69 @@ redef class Matrix
        #
        # This service aims to respect the world axes and logic of `gamnit`,
        # it may not correspond to all needs.
+       #
+       # The returned `Matrix` may be cached, it must not be modified.
        new gamnit_euler_rotation(pitch, yaw, roll: Float)
        do
+               if pitch == 0.0 and yaw == 0.0 and roll == 0.0 then
+                       return once new Matrix.identity(4)
+               end
+
+               if rotation_pitch == pitch and rotation_yaw == yaw and rotation_roll == roll then
+                       var rot = rotation_matrix_cache
+                       if rot != null then return rot
+               end
+
                var c1 = pitch.cos
                var s1 = pitch.sin
                var c2 = yaw.cos
                var s2 = yaw.sin
                var c3 = roll.cos
                var s3 = roll.sin
-               return new Matrix.from(
-                       [[          c2*c3,          -c2*s3,   -s2, 0.0],
-                        [ c1*s3+c3*s1*s2,  c1*c3-s1*s2*s3, c2*s1, 0.0],
-                        [-s1*s3+c1*c3*s2, -c3*s1-c1*s2*s3, c1*c2, 0.0],
-                        [            0.0,             0.0,   0.0, 1.0]])
+
+               var rot = new Matrix(4, 4)
+               rot.items.mat4_set(
+                                 c2*c3,          -c2*s3,   -s2, 0.0,
+                        c1*s3+c3*s1*s2,  c1*c3-s1*s2*s3, c2*s1, 0.0,
+                       -s1*s3+c1*c3*s2, -c3*s1-c1*s2*s3, c1*c2, 0.0,
+                                   0.0,             0.0,   0.0, 1.0)
+
+               rotation_matrix_cache = rot
+               rotation_pitch = pitch
+               rotation_yaw = yaw
+               rotation_roll = roll
+               return rot
        end
 end
+
+redef class NativeDoubleArray
+       fun mat4_set(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15: Float) `{
+               self[ 0] =  f0;
+               self[ 1] =  f1;
+               self[ 2] =  f2;
+               self[ 3] =  f3;
+
+               self[ 4] =  f4;
+               self[ 5] =  f5;
+               self[ 6] =  f6;
+               self[ 7] =  f7;
+
+               self[ 8] =  f8;
+               self[ 9] =  f9;
+               self[10] = f10;
+               self[11] = f11;
+
+               self[12] = f12;
+               self[13] = f13;
+               self[14] = f14;
+               self[15] = f15;
+       `}
+end
+
+redef class Sys
+
+       private var rotation_matrix_cache: nullable Matrix = null
+       private var rotation_pitch = 0.0
+       private var rotation_yaw = 0.0
+       private var rotation_roll = 0.0
+end
index b9e436a..61f0186 100644 (file)
@@ -60,9 +60,14 @@ class PerfMap
                return ts
        end
 
+       # Number of digits to the right of the decimal points in reports created by `to_s`
+       #
+       # Defaults to 4.
+       var precision = 4 is writable
+
        redef fun to_s
        do
-               var prec = 3
+               var prec = precision
 
                var table = new Map[String, Array[String]]
                for event, stats in self do
index a400619..33708b6 100644 (file)
@@ -44,7 +44,7 @@ void clock_gettime(clock_t clock_name, struct timespec *ts) {
 `}
 
 # Elapsed time representation.
-extern class Timespec `{struct timespec*`}
+private extern class Timespec `{struct timespec*`}
 
        # Init a new Timespec from `s` seconds and `ns` nanoseconds.
        new ( s, ns : Int ) `{
@@ -86,6 +86,18 @@ extern class Timespec `{struct timespec*`}
                return tv;
        `}
 
+       # Set `self` to `a` - `b`
+       fun minus(a, b: Timespec) `{
+               time_t s = a->tv_sec - b->tv_sec;
+               long ns = a->tv_nsec - b->tv_nsec;
+               if (ns < 0) {
+                       s -= 1;
+                       ns += 1000000000l;
+               }
+               self->tv_sec = s;
+               self->tv_nsec = ns;
+       `}
+
        # Number of whole seconds of elapsed time.
        fun sec : Int `{
                return self->tv_sec;
@@ -143,13 +155,15 @@ class Clock
        # TODO use less mallocs
 
        # Time at creation
-       protected var time_at_beginning = new Timespec.monotonic_now
+       private var time_at_beginning = new Timespec.monotonic_now
 
        # Time at last time a lapse method was called
-       protected var time_at_last_lapse = new Timespec.monotonic_now
+       private var time_at_last_lapse = new Timespec.monotonic_now
+
+       private var temp = new Timespec.monotonic_now
 
        # Smallest time frame reported by clock
-       fun resolution: Timespec `{
+       private fun resolution: Timespec `{
                struct timespec* tv = malloc( sizeof(struct timespec) );
 #if defined(__MACH__) && !defined(CLOCK_REALTIME)
                clock_serv_t cclock;
@@ -169,40 +183,40 @@ class Clock
        # Seconds since the creation of this instance
        fun total: Float
        do
-               var now = new Timespec.monotonic_now
-               var diff = now - time_at_beginning
-               var r = diff.to_f
-               diff.free
-               now.free
-               return r
+               var now = temp
+               now.update
+               now.minus(now, time_at_beginning)
+               return now.to_f
        end
 
        # Seconds since the last call to `lapse`
        fun lapse: Float
        do
-               var nt = new Timespec.monotonic_now
-               var dt = nt - time_at_last_lapse
-               var r = dt.to_f
-               dt.free
-               time_at_last_lapse.free
-               time_at_last_lapse = nt
+               var time_at_last_lapse = time_at_last_lapse
+               var now = temp
+               now.update
+               time_at_last_lapse.minus(now, time_at_last_lapse)
+               var r = time_at_last_lapse.to_f
+
+               self.temp = time_at_last_lapse
+               self.time_at_last_lapse = now
+
                return r
        end
 
        # Seconds since the last call to `lapse`, without resetting the lapse counter
        fun peek_lapse: Float
        do
-               var nt = new Timespec.monotonic_now
-               var dt = nt - time_at_last_lapse
-               var r = dt.to_f
-               nt.free
-               dt.free
-               return r
+               var now = temp
+               now.update
+               now.minus(now, time_at_last_lapse)
+               return now.to_f
        end
 
        redef fun finalize_once
        do
                time_at_beginning.free
                time_at_last_lapse.free
+               temp.free
        end
 end
index 17a294f..321e870 100644 (file)
@@ -15,6 +15,12 @@ Unlike generic lexical or syntactic highlighter, nitlight use semantic informati
 Common options of the Nit tools are understood.
 Here, only the specific one are indicated.
 
+### `--txt`
+Generate text with ANSI coloring escape sequences.
+
+Instead of HTML, this generate text files with ANSI escape sequence for colors.
+The options `-f`, `--line-id-prefix` and `--ast` are ignored in this mode.
+
 ### `-f`, `--fragment`
 Omit document header and footer.
 
@@ -59,6 +65,12 @@ By default, only the modules indicated on the command line are highlighted.
 
 With the `--full` option, all imported modules (even those in standard) are also precessed.
 
+### `--ast`
+Generate specific HTML elements for each Node of the AST.
+
+Additional `<span>` elements are generated for each node of the AST.
+The HTML generated can become quite large.
+
 # SEE ALSO
 
 The Nit language documentation and the source code of its tools and libraries may be downloaded from <http://nitlanguage.org>
index 139afaf..230bf81 100644 (file)
@@ -22,7 +22,7 @@ import commands_base
 import model::model_collect
 import modelize
 import modelbuilder
-import highlight
+import htmlight
 import doc_down
 
 # Retrieve the MDoc related to a MEntity
@@ -428,8 +428,8 @@ class CmdCode
                var node = self.node
                if node == null then return null
                if format == "html" then
-                       var hl = new HighlightVisitor
-                       hl.enter_visit node
+                       var hl = new HtmlightVisitor
+                       hl.highlight_node node
                        return hl.html
                end
                # TODO make a raw visitor
index 5951f8e..7bb330b 100644 (file)
@@ -16,7 +16,7 @@
 module doc_down
 
 import markdown
-import highlight
+import htmlight
 private import parser_util
 
 redef class MDoc
@@ -160,9 +160,9 @@ class NitdocDecorator
                        return
                end
                v.add "<pre class=\"nitcode\"><code>"
-               var hl = new HighlightVisitor
+               var hl = new HtmlightVisitor
                hl.line_id_prefix = ""
-               hl.enter_visit(ast)
+               hl.highlight_node(ast)
                v.add(hl.html)
                v.add "</code></pre>\n"
        end
@@ -177,9 +177,9 @@ class NitdocDecorator
                        append_code(v, text, from, to)
                else
                        v.add "<code class=\"nitcode\">"
-                       var hl = new HighlightVisitor
+                       var hl = new HtmlightVisitor
                        hl.line_id_prefix = ""
-                       hl.enter_visit(ast)
+                       hl.highlight_node(ast)
                        v.add(hl.html)
                end
                v.add "</code>"
@@ -217,8 +217,8 @@ private class InlineDecorator
                        return
                end
                v.add "<code class=\"nitcode\">"
-               var hl = new HighlightVisitor
-               hl.enter_visit(ast)
+               var hl = new HtmlightVisitor
+               hl.highlight_node(ast)
                v.add(hl.html)
                v.add "</code>"
        end
index 88e1b94..da179be 100644 (file)
 module nitlight_as_a_service is example
 
 import frontend
-import highlight
+import htmlight
 import nitcorn
 import nitcorn::log
 import template
 import json::serialization_write
 
-# Nitcorn service to hightlight code
+# Nitcorn service to highlight code
 #
 # It's a single stand-alone page that has to form to itself.
 class HighlightAction
@@ -32,13 +32,13 @@ class HighlightAction
 
        redef fun answer(http_request, turi)
        do
-               var hl = new HighlightVisitor
+               var hl = new HtmlightVisitor
                var page = new Template
 
                # There is code? Process it
                var code = http_request.post_args.get_or_null("code")
                var hlcode = null
-               if code != null then hlcode = hightlightcode(hl, code)
+               if code != null then hlcode = hl.highlightcode(code)
 
                if http_request.post_args.get_or_null("json") == "true" and hlcode != null then
                        var response = new HttpResponse(200)
index f654c3e..d2411fb 100644 (file)
 module highlight
 
 import frontend
-import html
-import pipeline
 import astutil
-import serialization
-
-# Fully process `content` as a Nit source file.
-#
-# Set `print_errors = true` to print errors in the code to the console.
-fun hightlightcode(hl: HighlightVisitor, content: String, print_errors: nullable Bool): HLCode
-do
-       # Prepare a stand-alone tool context
-       var tc = new ToolContext
-       tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
-       tc.keep_going = true # no exit, obviously
-       if print_errors != true then tc.opt_warn.value = -1 # no output
-
-       # Prepare an stand-alone model and model builder.
-       # Unfortunately, models are enclosing and append-only.
-       # There is no way (yet?) to have a shared module `core` with
-       # isolated and throwable user modules.
-       var model = new Model
-       var mb = new ModelBuilder(model, tc)
-
-       # Parse the code
-       var source = new SourceFile.from_string("", content + "\n")
-       var lexer = new Lexer(source)
-       var parser = new Parser(lexer)
-       var tree = parser.parse
-
-       var hlcode = new HLCode(hl, content, source)
-
-       # Check syntax error
-       var eof = tree.n_eof
-       if eof isa AError then
-               mb.error(eof, eof.message)
-               hl.hightlight_source(source)
-               return hlcode
-       end
-       var amodule = tree.n_base.as(not null)
-
-       # Load the AST as a module in the model
-       # Then process it
-       mb.load_rt_module(null, amodule, "")
-       mb.run_phases
-
-       # Highlight the processed module
-       hl.enter_visit(amodule)
-       return hlcode
-end
-
-# A standalone highlighted piece of code
-class HLCode
-       super Serializable
-
-       # The highlighter used
-       var hl: HighlightVisitor
-
-       # The raw code source
-       var content: String
-
-       # The pseudo source-file
-       var source: SourceFile
-
-       # JavaScript code to update an existing codemirror editor.
-       fun code_mirror_update: Template
-       do
-
-               var res = new Template
-               res.add """
-       function nitmessage() {
-               editor.operation(function(){
-                       for (var i = 0; i < widgets.length; ++i)
-                             editor.removeLineWidget(widgets[i]);
-                       widgets.length = 0;
-"""
-
-               for m in source.messages do
-                       res.add """
-                       var l = document.createElement("div");
-                       l.className = "lint-error"
-                       l.innerHTML = "<span class='glyphicon glyphicon-warning-sign lint-error-icon'></span> {{{m.text.html_escape}}}";
-                       var w = editor.addLineWidget({{{m.location.line_start-1}}}, l);
-                       widgets.push(w);
-"""
-               end
-               res.add """});}"""
-               return res
-       end
-
-       redef fun core_serialize_to(v)
-       do
-               v.serialize_attribute("code", hl.html.write_to_string)
-               var msgs = new Array[Map[String, Serializable]]
-               for m in source.messages do
-                       var o = new Map[String, Serializable]
-                       msgs.add o
-                       o["line"] = m.location.line_start-1
-                       o["message"] = m.text
-               end
-               v.serialize_attribute("messages", msgs)
-       end
-end
+import console
 
 # Visitor used to produce a HTML tree based on a AST on a `Source`
-class HighlightVisitor
-       # The root of the HTML hierarchy
-       var html = new HTMLTag("span")
-
-       # Should the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
-       #
-       # Used to have a really huge and verbose HTML (mainly for debug)
-       var with_ast = false is writable
-
-       # Prefixes used in generated IDs for line `<span>` elements.
-       # Useful if more than one highlighted code is present in the same HTML document.
-       #
-       # If set to the empty string, id for lines are disabled.
-       #
-       # Is `"L"` by default.
-       var line_id_prefix = "L" is writable
-
+class AbstractHighlightVisitor
        # The first line to generate, null if start at the first line
        var first_line: nullable Int = null is writable
 
@@ -148,27 +32,6 @@ class HighlightVisitor
        # default: true
        var show_messages = true is writable
 
-       # When highlighting a node, attach a full popupable infobox, if any.
-       #
-       # If `false`, only a simple `title` tooltip is used.
-       #
-       # default: true
-       var show_infobox = true is writable
-
-       # A reference to an entity used in generated `<a>` elements.
-       #
-       # It is used to refer to some specific entities when generating links.
-       # If `null` is returned, then no link are generated and `<a>` elements become `<span>`.
-       #
-       # By default, `null` is returned.
-       # Clients are therefore encouraged to redefine the method in a subclass to control where entities should link to.
-       fun hrefto(entity: MEntity): nullable String do return null
-
-       init
-       do
-               html.add_class("nitcode")
-       end
-
        # When highlighting a node, also consider the loose tokens around it.
        #
        # Loose tokens are tokens discarded from the AST but attached before
@@ -188,9 +51,8 @@ class HighlightVisitor
        # Default: false
        var include_whole_lines = false is writable
 
-       # The entry-point of the highlighting.
-       # Will fill `html` with the generated HTML content.
-       fun enter_visit(n: ANode)
+       # Highlight a AST element.
+       fun highlight_node(n: ANode)
        do
                n.parentize_tokens
 
@@ -234,953 +96,62 @@ class HighlightVisitor
                        l = l.last_real_token_in_line
                end
 
-               htmlize(f, l)
-       end
-
-       private fun full_tag(anode: ANode, hv: HighlightVisitor): nullable HTMLTag
-       do
-               var tag = anode.make_tag(hv)
-               if tag == null then return null
-               var infobox = anode.infobox(hv)
-               if infobox == null and anode isa Token then
-                       var pa = anode.parent
-                       if pa != null then
-                               infobox = pa.decorate_tag(hv, tag, anode)
-                       end
-               end
-               if infobox != null and not show_infobox then
-                       tag.attr("title", infobox.title)
-                       tag.classes.add "titled"
-                       infobox = null
-               end
-               var messages = anode.location.messages
-               if messages != null and show_messages then
-                       tag.css("border-bottom", "solid 2px red")
-                       if infobox == null then
-                               infobox = new HInfoBox(hv, "Messages")
-                       end
-                       var c = infobox.new_dropdown("{messages.length} message(s)", "")
-                       for m in messages do
-                               c.open("li").append(m.text)
-                       end
-               end
-               if infobox != null then
-                       tag.attach_infobox(infobox)
-               end
-               return tag
+               do_highlight(f, l)
        end
 
        # Highlight a full lexed source file.
        #
        # REQUIRE `source.first_token != null`
-       fun hightlight_source(source: SourceFile)
-       do
-               htmlize(source.first_token.as(not null), null)
-       end
-
-       # Produce HTML between two tokens
-       protected fun htmlize(first_token: Token, last_token: nullable Token)
-       do
-               var stack2 = new Array[HTMLTag]
-               var stack = new Array[Prod]
-               var line = 0
-               var c: nullable Token = first_token
-               var hv = self
-               while c != null do
-                       var starting
-
-                       # Handle start of line
-                       var cline = c.location.line_start
-                       if cline != line then
-                               # Handle starting block productions,
-                               # Because c could be a detached token, get prods in
-                               # the first AST token
-                               var c0 = c.first_token_in_line
-                               starting = null
-                               if c0 != null then starting = c0.starting_prods
-                               if starting != null then for p in starting do
-                                       if not p.is_block then continue
-                                       var tag = full_tag(p, hv)
-                                       if tag == null then continue
-                                       tag.add_class("foldable")
-                                       stack2.add(html)
-                                       html.add tag
-                                       html = tag
-                                       stack.add(p)
-                               end
-
-                               # Add a div for the whole line
-                               var tag = new HTMLTag("span")
-                               var p = line_id_prefix
-                               if p != "" then tag.attrs["id"] = "{p}{cline}"
-                               tag.classes.add "line"
-                               stack2.add(html)
-                               html.add tag
-                               html = tag
-                               line = cline
-                       end
-
-                       # Add the blank, verbatim
-                       html.add_raw_html c.blank_before
-
-                       # Handle starting span production
-                       starting = c.starting_prods
-                       if starting != null then for p in starting do
-                               if not p.is_span then continue
-                               var tag = full_tag(p, hv)
-                               if tag == null then continue
-                               stack2.add(html)
-                               html.add tag
-                               html = tag
-                               stack.add(p)
-                       end
-
-                       # Add the token
-                       if c isa TEol then
-                               html.append "\n"
-                       else
-                               var tag = full_tag(c, hv)
-                               if tag != null then html.add tag
-                       end
-
-                       # Handle ending span productions
-                       var ending = c.ending_prods
-                       if ending != null then for p in ending do
-                               if not p.is_span then continue
-                               if stack.is_empty or p != stack.last then continue
-                               stack.pop
-                               html = stack2.pop
-                       end
-
-                       # Handle end of line and end of file
-                       var n = c.next_token
-                       if c == last_token then n = null
-                       if n == null or n.location.line_start != line  then
-                               # closes the line div
-                               html = stack2.pop
-
-                               # close the block production divs
-                               var c0 = c.last_token_in_line
-                               ending = null
-                               if c0 != null then ending = c0.ending_prods
-                               if ending != null then for p in ending do
-                                       if not p.is_block then continue
-                                       if stack.is_empty or p != stack.last then continue
-                                       stack.pop
-                                       html = stack2.pop
-                               end
-                       end
-
-                       c = n
-               end
-               if not stack2.is_empty then html = stack2.first
-       end
-
-       # Return a default CSS content related to CSS classes used in the `html` tree.
-       # Could be inlined in the `.html` file of saved as a specific `.css` file.
-       fun css_content: String
+       fun highlight_source(source: SourceFile)
        do
-               return """
-.nitcode a { color: inherit; cursor:pointer; }
-.nitcode .titled:hover { text-decoration: underline; } /* underline titles */
-.nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
-.nitcode .foldable { display: block } /* for block productions*/
-.nitcode .line{ display: block } /* for lines */
-.nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
-.nitcode :target { background-color: #FFF3C2 } /* target highlight*/
-/* lexical raw tokens. independent of usage or semantic: */
-.nitcode .nc_c { color: gray; font-style: italic; } /* comment */
-.nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
-.nitcode .nc_k { font-weight: bold; } /* keyword */
-.nitcode .nc_o {} /* operator */
-.nitcode .nc_i {} /* standard identifier */
-.nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
-.nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
-.nitcode .nc_l { color: #009999; } /* char and number literal */
-.nitcode .nc_s { color: #8F1546; } /* string literal */
-/* syntactic token usage. added because of their position in the AST */
-.nitcode .nc_ast { color: blue; } /* assert label */
-.nitcode .nc_la { color: blue; } /* break/continue label */
-.nitcode .nc_m { color: #445588; } /* module name */
-/* syntactic groups */
-.nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
-  .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
-  .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
-.nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
-.nitcode .nc_cdef {} /* A whole class definition */
-.nitcode .nc_pdef {} /* A whole property definition */
-/* semantic token usage */
-.nitcode .nc_v { font-style: italic; } /* local variable or parameter */
-.nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
-
-.nitcode .nc_error { border: 1px red solid;} /* not used */
-.popover { max-width: 800px !important; }
-"""
+               do_highlight(source.first_token.as(not null), null)
        end
 
-       # Additional content to inject in the <head> tag
-       # Note: does not include `css_content`; handle it yourself.
-       fun head_content: String
-       do
-               return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
-       end
-
-       # Additional content to inject just before the closing </body> tag
-       fun foot_content: String
-       do
-               return """
-<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
-<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
-<script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
-       end
-end
-
-redef class HTMLTag
-       # Attach the infobox to the node by using BootStrap popover
-       fun attach_infobox(infobox: HInfoBox)
-       do
-               classes.add("popupable")
-               attrs["title"] = infobox.title
-               var href = infobox.href
-               if href != null then
-                       attrs["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
-               end
-               attrs["data-content"] = infobox.content.write_to_string
-               attrs["data-toggle"] = "popover"
-       end
+       # Low-level highlighting between 2 tokens
+       protected fun do_highlight(first_token: Token, last_token: nullable Token) is abstract
 end
 
+# Text-based highlighter that use ANSI escape sequence for colors
+class AnsiHighlightVisitor
+       super AbstractHighlightVisitor
 
-# A generic information container that can be used to decorate AST entities
-class HInfoBox
-       # The visitor used for contextualisation, if needed 
-       var visitor: HighlightVisitor
-
-       # A short title for the AST element
-       var title: String
-
-       # The primary link where the entity points
-       # null if no link
-       var href: nullable String = null
-
-       # The content of the popuped infobox
-       var content = new HTMLTag("div")
-
-       # Append a new field in the popuped infobox
-       fun new_field(title: String): HTMLTag
-       do
-               content.open("b").text(title)
-               content.append(" ")
-               var res = content.open("span")
-               content.open("br")
-               return res
-       end
-
-       # Append a new dropdown in the popuped content
-       fun new_dropdown(title, text: String, text_is_html: nullable Bool): HTMLTag
-       do
-               content.add_raw_html """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
-               content.append(title)
-               content.add_raw_html "</b> "
-               if text_is_html == true then
-                       content.add_raw_html(text)
-               else content.append(text)
-               content.add_raw_html """<span class="caret"></span></a>"""
-               var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
-               content.add_raw_html "</div>"
-               return res
-       end
-end
-
-##
-
-# Model entity or whatever that can produce an infobox
-interface HInfoBoxable
-       # An new infobox documenting the entity
-       fun infobox(v: HighlightVisitor): HInfoBox is abstract
-end
-
-redef class MDoc
-       # Append an entry for the doc in the given infobox
-       fun fill_infobox(res: HInfoBox)
-       do
-               if content.length < 2 then
-                       res.new_field("doc").text(content.first)
-                       return
-               end
-               var c = res.new_dropdown("doc", content.first)
-               for x in content.iterator.skip_head(1) do
-                       c.append x
-                       c.add_raw_html "<br>"
-               end
-       end
-end
-
-redef class MEntity
-       super HInfoBoxable
-
-       # A HTML version of `to_s` with hyper-links.
-       #
-       # By default, `linkto_text(v, to_s)` is used, c.f. see `linkto_text`.
-       #
-       # For some complex entities, like generic types, multiple `<a>` and `<span>` elements can be generated.
-       # E.g. `Array[Int]` might become `<a>Array</a>[<a>Int</a>]` with the correct `href` attributes
-       # provided  by `v.hrefto`.
-       fun linkto(v: HighlightVisitor): HTMLTag do return linkto_text(v, to_s)
-
-       # Link to the `self` with a specific text.
-       #
-       # The whole text is linked with a single `<a>` element.
-       #
-       # The `href` used is provided by `v.hrefto`.
-       # If `href` is null then a `<span>` element is used instead of `<a>`.
-       fun linkto_text(v: HighlightVisitor, text: String): HTMLTag
-       do
-               var href = v.hrefto(self)
-               if href == null then
-                       return (new HTMLTag("span")).text(text)
-               end
-               return (new HTMLTag("a")).attr("href", href).text(text)
-       end
-
-       # Append an entry for the doc in the given infobox
-       private fun add_doc_to_infobox(res: HInfoBox)
-       do
-               var mdoc = mdoc_or_fallback
-               if mdoc != null then mdoc.fill_infobox(res)
-       end
-end
-
-redef class MModule
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, "module {name}")
-               res.href = v.hrefto(self)
-               res.new_field("module").add(linkto(v))
-               add_doc_to_infobox(res)
-               if in_importation.greaters.length > 1 then
-                       var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules")
-                       for x in in_importation.greaters do
-                               if x == self then continue
-                               c.open("li").add x.linkto(v)
-                       end
-               end
-               return res
-       end
-
-       redef fun linkto(v) do return linkto_text(v, name)
-end
-
-redef class MClassDef
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, "class {mclass.name}")
-               res.href = v.hrefto(self)
-               if is_intro then
-                       res.new_field("class").text(mclass.name)
-               else
-                       res.new_field("redef class").text(mclass.name)
-                       res.new_field("intro").add mclass.intro.linkto_text(v, "in {mclass.intro_mmodule.to_s}")
-               end
-               add_doc_to_infobox(res)
-
-               var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return res
-
-               if in_hierarchy.greaters.length > 1 then
-                       var c = res.new_dropdown("hier", "super-classes")
-                       for x in in_hierarchy.greaters do
-                               if x == self then continue
-                               if not x.is_intro then continue
-                               c.open("li").add x.linkto(v)
-                       end
-               end
-               if in_hierarchy.smallers.length > 1 then
-                       var c = res.new_dropdown("hier", "sub-classes")
-                       for x in in_hierarchy.smallers do
-                               if x == self then continue
-                               if not x.is_intro then continue
-                               c.open("li").add x.linkto(v)
-                       end
-               end
-               if mclass.mclassdefs.length > 1 then
-                       var c = res.new_dropdown("redefs", "refinements")
-                       for x in mclass.mclassdefs do
-                               if x == self then continue
-                               c.open("li").add x.linkto_text(v, "in {x.mmodule}")
-                       end
-               end
-               return res
-       end
-end
-
-redef class MPropDef
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, to_s)
-               res.href = v.hrefto(self)
-               if self isa MMethodDef then
-                       var msignature = self.msignature
-                       if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v)
-               else if self isa MAttributeDef then
-                       var static_mtype = self.static_mtype
-                       if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v)
-               else if self isa MVirtualTypeDef then
-                       var bound = self.bound
-                       if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v)
-               else
-                       res.new_field("wat?").append(mproperty.name)
-               end
-
-               if is_intro then
-               else
-                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
-               end
-               add_doc_to_infobox(res)
-               if mproperty.mpropdefs.length > 1 then
-                       var c = res.new_dropdown("redef", "redefinitions")
-                       for x in mproperty.mpropdefs do
-                               c.open("li").add x.linkto_text(v, "in {x.mclassdef}")
-                       end
-               end
-
-               return res
-       end
-end
-
-redef class MClassType
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, to_s)
-               res.href = v.hrefto(self)
-               res.new_field("class").add mclass.intro.linkto(v)
-               add_doc_to_infobox(res)
-               return res
-       end
-       redef fun linkto(v)
-       do
-               return mclass.intro.linkto(v)
-       end
-end
-redef class MVirtualType
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, to_s)
-               res.href = v.hrefto(mproperty)
-               var p = mproperty
-               res.new_field("virtual type").add p.intro.linkto(v)
-               add_doc_to_infobox(res)
-               return res
-       end
-       redef fun linkto(v)
-       do
-               return mproperty.intro.linkto(v)
-       end
-end
-redef class MParameterType
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, to_s)
-               res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto(v)
-               return res
-       end
-end
-
-redef class MNullableType
-       redef fun infobox(v)
-       do
-               return mtype.infobox(v)
-       end
-       redef fun linkto(v)
-       do
-               var res = new HTMLTag("span")
-               res.append("nullable ").add(mtype.linkto(v))
-               return res
-       end
-end
-
-redef class MNotNullType
-       redef fun infobox(v)
-       do
-               return mtype.infobox(v)
-       end
-       redef fun linkto(v)
-       do
-               var res = new HTMLTag("span")
-               res.append("not null ").add(mtype.linkto(v))
-               return res
-       end
-end
+       # The produced highlighting
+       var result = new Template
 
-redef class MNullType
-       redef fun infobox(v)
+       redef fun do_highlight(f, l)
        do
-               var res = new HInfoBox(v, to_s)
-               return res
-       end
-       redef fun linkto(v)
-       do
-               var res = new HTMLTag("span")
-               res.append("null")
-               return res
-       end
-end
+               var c
+               c = f
+               while c != null do
+                       if c != f then result.add(c.blank_before)
+                       result.add c.ansi_colored
 
-redef class MSignature
-       redef fun linkto(v)
-       do
-               var res = new HTMLTag("span")
-               var first = true
-               if not mparameters.is_empty then
-                       res.append "("
-                       for p in mparameters do
-                               if first then
-                                       first = false
-                               else
-                                       res.append ", "
-                               end
-                               res.append p.name
-                               res.append ": "
-                               res.add p.mtype.linkto(v)
+                       if c == l then
+                               c = null
+                       else
+                               c = c.next_token
                        end
-                       res.append ")"
-               end
-               var ret = return_mtype
-               if ret != null then
-                       res.append ": "
-                       res.add ret.linkto(v)
                end
-               return res
-       end
-end
-
-redef class CallSite
-       redef fun infobox(v)
-       do
-               var res = new HInfoBox(v, "call {mpropdef}")
-               res.href = v.hrefto(mpropdef)
-               res.new_field("call").add(mpropdef.linkto(v)).add(msignature.linkto(v))
-               if mpropdef.is_intro then
-               else
-                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
-               end
-               add_doc_to_infobox(res)
-
-               return res
-       end
-       redef fun linkto(v)
-       do
-               return mpropdef.linkto(v)
-       end
-end
-
-redef class Variable
-       super HInfoBoxable
-       redef fun infobox(v)
-       do
-               var declared_type = self.declared_type
-               if declared_type == null then
-                       var res = new HInfoBox(v, "{name}")
-                       res.new_field("local var").append("{name}")
-                       return res
-               end
-               var res = new HInfoBox(v, "{name}: {declared_type}")
-               res.new_field("local var").append("{name}:").add(declared_type.linkto(v))
-               return res
-       end
-end
-
-
-##
-
-redef class ANode
-       # Optionally creates a tag that encapsulate the AST element on HTML rendering
-       protected fun make_tag(v: HighlightVisitor): nullable HTMLTag do return null
-
-       # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
-       protected fun decorate_tag(v: HighlightVisitor, res: HTMLTag, token: Token): nullable HInfoBox
-       do
-               #debug("no decoration for {token.inspect}")
-               #res.add_class("nc_error")
-               return null
-       end
-
-       # Return a optional infobox
-       fun infobox(v: HighlightVisitor): nullable HInfoBox do return null
-end
-
-redef class AQclassid
-       redef fun decorate_tag(v, res, token)
-       do
-               if token != n_id then return null
-               var parent = self.parent
-               if parent == null then return null
-               return parent.decorate_tag(v, res, token)
-       end
-end
-
-redef class AQid
-       redef fun decorate_tag(v, res, token)
-       do
-               if token != n_id then return null
-               var parent = self.parent
-               if parent == null then return null
-               return parent.decorate_tag(v, res, token)
-       end
-end
-
-redef class AStdClassdef
-       redef fun make_tag(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_cdef")
-               var md = mclassdef
-               if md != null then res.attr("id", md.to_s)
-               return res
-       end
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TClassid then return null
-               res.add_class("nc_def")
-
-               var md = mclassdef
-               if md == null then return null
-               return md.infobox(v)
-       end
-end
-redef class APropdef
-       redef fun make_tag(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_pdef")
-               var mpd
-               mpd = mpropdef
-               if mpd != null then
-                       #res.add(tag(mpd))
-                       res.attr("id", mpd.to_s)
-               end
-               if self isa AAttrPropdef then
-                       mpd = mreadpropdef
-                       if mpd != null then res.add(tag(mpd))
-                       mpd = mwritepropdef
-                       if mpd != null then res.add(tag(mpd))
-               end
-               return res
-       end
-
-       private fun tag(mpd: MPropDef): HTMLTag
-       do
-               var a = new HTMLTag("a")
-               a.attr("id", mpd.to_s)
-               return a
        end
 end
 
 redef class Token
-       # Produce an HTMLTag with the correct contents and CSS classes
-       # Subclasses can redefine it to decorate the tag
-       redef fun make_tag(v): HTMLTag
-       do
-               var res = new HTMLTag("span")
-               res.text(text)
-               return res
-       end
-end
-
-redef class TokenKeyword
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_k")
-               return res
-       end
-end
-redef class TokenOperator
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_o")
-               return res
-       end
-end
-
-redef class AVarFormExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               if token != n_id then return null
-               var variable = self.variable
-               if variable == null then return null
-               res.add_class("nc_v")
-               return variable.infobox(v)
-       end
+       # Return the ANSI colored text
+       fun ansi_colored: String do return text
 end
 
-redef class AVardeclExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               if token != n_id then return null
-               var variable = self.variable
-               if variable == null then return null
-               res.add_class("nc_v")
-               return variable.infobox(v)
-       end
-end
-
-redef class AForGroup
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               var vs = variables
-               if vs == null then return null
-               res.add_class("nc_v")
-               var idx = n_ids.index_of(token)
-               var variable = vs[idx]
-               return variable.infobox(v)
-       end
-end
-
-redef class AParam
-       redef fun decorate_tag(v, res, token)
-       do
-               if token != n_id then return null
-               var mp = mparameter
-               if mp == null then return null
-               var variable = self.variable
-               if variable == null then return null
-               res.add_class("nc_v")
-               return variable.infobox(v)
-       end
-end
-
-redef class AAssertExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               res.add_class("nc_ast")
-               return null
-       end
-end
-
-redef class ALabel
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               res.add_class("nc_la")
-               return null
-       end
-end
-
-redef class ASendExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               var callsite = self.callsite
-               if callsite == null then return null
-               return callsite.infobox(v)
-       end
-end
-
-redef class ANewExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               var callsite = self.callsite
-               if callsite == null then return null
-               return callsite.infobox(v)
-       end
-end
-
-redef class AAssignOp
-       redef fun decorate_tag(v, res, token)
-       do
-               var p = parent
-               assert p isa AReassignFormExpr
-
-               var callsite = p.reassign_callsite
-               if callsite == null then return null
-               return callsite.infobox(v)
-       end
-end
-
-redef class AModuleName
-       redef fun decorate_tag(v, res, token)
-       do
-               var p = parent
-               if p == null then return null
-               return p.decorate_tag(v, res, token)
-       end
-end
-
-redef class AModuledecl
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               res.add_class("nc_def")
-               res.add_class("nc_m")
-               var p = parent
-               assert p isa AModule
-               var mm = p.mmodule
-               if mm == null then return null
-               return mm.infobox(v)
-       end
+redef class TComment
+       redef fun ansi_colored do return super.blue
 end
 
-redef class AStdImport
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               res.add_class("nc_m")
-               var mm = mmodule
-               if mm == null then return null
-               return mm.infobox(v)
-       end
-end
-redef class AAttrPropdef
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TId then return null
-               res.add_class("nc_def")
-               var mpd: nullable MPropDef
-               mpd = mreadpropdef
-               if mpd == null then mpd = mpropdef
-               if mpd == null then return null
-               return mpd.infobox(v)
-       end
+redef class TokenKeyword
+       redef fun ansi_colored do return super.yellow
 end
 
-redef class TId
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_i")
-               return res
-       end
-end
-redef class AMethid
-       redef fun make_tag(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_def")
-               return res
-       end
-       redef fun decorate_tag(v, res, token)
-       do
-               return null
-               # nothing to decorate
-       end
-       redef fun infobox(v)
-       do
-               var p = parent
-               if not p isa AMethPropdef then return null
-               var mpd = p.mpropdef
-               if mpd == null then return null
-               return mpd.infobox(v)
-       end
-end
-redef class TAttrid
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_a")
-               return res
-       end
-end
-redef class AAttrFormExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TAttrid then return null
-               var p = mproperty
-               if p == null then return null
-               return p.intro.infobox(v)
-       end
-end
 redef class TClassid
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_t")
-               return res
-       end
-end
-redef class AType
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TClassid then return null
-               var mt = mtype
-               if mt == null then return null
-               mt = mt.undecorate
-               if mt isa MFormalType then
-                       res.add_class("nc_vt")
-               end
-               return mt.infobox(v)
-       end
-end
-redef class AFormaldef
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TClassid then return null
-               res.add_class("nc_vt")
-               var mtype = self.mtype
-               if mtype == null then return null
-               return mtype.infobox(v)
-       end
-end
-redef class ATypePropdef
-       redef fun decorate_tag(v, res, token)
-       do
-               if not token isa TClassid then return null
-               res.add_class("nc_def")
-               var md = mpropdef
-               if md == null then return null
-               return md.infobox(v)
-       end
-end
-redef class TComment
-       redef fun make_tag(v)
-       do
-               var res = super
-               if is_loose then
-                       res.add_class("nc_c")
-               end
-               return res
-       end
-end
-redef class ADoc
-       redef fun make_tag(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_d")
-               return res
-       end
+       redef fun ansi_colored do return super.green
 end
+
 redef class TokenLiteral
-       redef fun make_tag(v)
-       do
-               var res = super
-               res.add_class("nc_l")
-               return res
-       end
-end
-redef class ASuperstringExpr
-       redef fun make_tag(v)
-       do
-               var res = new HTMLTag("span")
-               res.add_class("nc_ss")
-               return res
-       end
-end
-redef class AStringFormExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               # Workaround to tag strings
-               res.classes.remove("nc_l")
-               res.add_class("nc_s")
-               return super
-       end
-end
-redef class AExpr
-       redef fun decorate_tag(v, res, token)
-       do
-               var t = mtype
-               if t == null then return null
-               return t.infobox(v)
-       end
+       redef fun ansi_colored do return super.red
 end
diff --git a/src/htmlight.nit b/src/htmlight.nit
new file mode 100644 (file)
index 0000000..eb6f282
--- /dev/null
@@ -0,0 +1,1099 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Highlighting of Nit AST with HTML
+module htmlight
+
+import highlight
+import html
+import pipeline
+import serialization
+
+# A standalone highlighted piece of code
+class HLCode
+       super Serializable
+
+       # The highlighter used
+       var hl: HtmlightVisitor
+
+       # The raw code source
+       var content: String
+
+       # The pseudo source-file
+       var source: SourceFile
+
+       # JavaScript code to update an existing codemirror editor.
+       fun code_mirror_update: Template
+       do
+
+               var res = new Template
+               res.add """
+       function nitmessage() {
+               editor.operation(function(){
+                       for (var i = 0; i < widgets.length; ++i)
+                             editor.removeLineWidget(widgets[i]);
+                       widgets.length = 0;
+"""
+
+               for m in source.messages do
+                       res.add """
+                       var l = document.createElement("div");
+                       l.className = "lint-error"
+                       l.innerHTML = "<span class='glyphicon glyphicon-warning-sign lint-error-icon'></span> {{{m.text.html_escape}}}";
+                       var w = editor.addLineWidget({{{m.location.line_start-1}}}, l);
+                       widgets.push(w);
+"""
+               end
+               res.add """});}"""
+               return res
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("code", hl.html.write_to_string)
+               var msgs = new Array[Map[String, Serializable]]
+               for m in source.messages do
+                       var o = new Map[String, Serializable]
+                       msgs.add o
+                       o["line"] = m.location.line_start-1
+                       o["message"] = m.text
+               end
+               v.serialize_attribute("messages", msgs)
+       end
+end
+
+# Visitor used to produce a HTML tree based on a AST on a `Source`
+class HtmlightVisitor
+       super AbstractHighlightVisitor
+
+       # The root of the HTML hierarchy
+       var html = new HTMLTag("span")
+
+       # Should the HTML include a nested `<span class"{type_of_node}">` element for each `ANode` of the AST?
+       #
+       # Used to have a really huge and verbose HTML (mainly for debug)
+       var with_ast = false is writable
+
+       # Prefixes used in generated IDs for line `<span>` elements.
+       # Useful if more than one highlighted code is present in the same HTML document.
+       #
+       # If set to the empty string, id for lines are disabled.
+       #
+       # Is `"L"` by default.
+       var line_id_prefix = "L" is writable
+
+       # When highlighting a node, attach a full popupable infobox, if any.
+       #
+       # If `false`, only a simple `title` tooltip is used.
+       #
+       # default: true
+       var show_infobox = true is writable
+
+       # A reference to an entity used in generated `<a>` elements.
+       #
+       # It is used to refer to some specific entities when generating links.
+       # If `null` is returned, then no link are generated and `<a>` elements become `<span>`.
+       #
+       # By default, `null` is returned.
+       # Clients are therefore encouraged to redefine the method in a subclass to control where entities should link to.
+       fun hrefto(entity: MEntity): nullable String do return null
+
+       init
+       do
+               html.add_class("nitcode")
+       end
+
+       private fun full_tag(anode: ANode): nullable HTMLTag
+       do
+               var tag = anode.make_tag(self)
+               if tag == null then return null
+               var infobox = anode.infobox(self)
+               if infobox == null and anode isa Token then
+                       var pa = anode.parent
+                       if pa != null then
+                               infobox = pa.decorate_tag(self, tag, anode)
+                       end
+               end
+               if infobox != null and not show_infobox then
+                       tag.attr("title", infobox.title)
+                       tag.classes.add "titled"
+                       infobox = null
+               end
+               var messages = anode.location.messages
+               if messages != null and show_messages then
+                       tag.css("border-bottom", "solid 2px red")
+                       if infobox == null then
+                               infobox = new HInfoBox(self, "Messages")
+                       end
+                       var c = infobox.new_dropdown("{messages.length} message(s)", "")
+                       for m in messages do
+                               c.open("li").append(m.text)
+                       end
+               end
+               if infobox != null then
+                       tag.attach_infobox(infobox)
+               end
+               return tag
+       end
+
+       # Low-level highlighting between 2 tokens
+       redef fun do_highlight(first_token, last_token)
+       do
+               var stack2 = new Array[HTMLTag]
+               var stack = new Array[Prod]
+               var line = 0
+               var c: nullable Token = first_token
+               while c != null do
+                       var starting
+
+                       # Handle start of line
+                       var cline = c.location.line_start
+                       if cline != line then
+                               # Handle starting block productions,
+                               # Because c could be a detached token, get prods in
+                               # the first AST token
+                               var c0 = c.first_token_in_line
+                               starting = null
+                               if c0 != null then starting = c0.starting_prods
+                               if starting != null then for p in starting do
+                                       if not p.is_block then continue
+                                       var tag = full_tag(p)
+                                       if tag == null then continue
+                                       tag.add_class("foldable")
+                                       stack2.add(html)
+                                       html.add tag
+                                       html = tag
+                                       stack.add(p)
+                               end
+
+                               # Add a div for the whole line
+                               var tag = new HTMLTag("span")
+                               var p = line_id_prefix
+                               if p != "" then tag.attrs["id"] = "{p}{cline}"
+                               tag.classes.add "line"
+                               stack2.add(html)
+                               html.add tag
+                               html = tag
+                               line = cline
+                       end
+
+                       # Add the blank, verbatim
+                       html.add_raw_html c.blank_before
+
+                       # Handle starting span production
+                       starting = c.starting_prods
+                       if starting != null then for p in starting do
+                               if not p.is_span then continue
+                               var tag = full_tag(p)
+                               if tag == null then continue
+                               stack2.add(html)
+                               html.add tag
+                               html = tag
+                               stack.add(p)
+                       end
+
+                       # Add the token
+                       if c isa TEol then
+                               html.append "\n"
+                       else
+                               var tag = full_tag(c)
+                               if tag != null then html.add tag
+                       end
+
+                       # Handle ending span productions
+                       var ending = c.ending_prods
+                       if ending != null then for p in ending do
+                               if not p.is_span then continue
+                               if stack.is_empty or p != stack.last then continue
+                               stack.pop
+                               html = stack2.pop
+                       end
+
+                       # Handle end of line and end of file
+                       var n = c.next_token
+                       if c == last_token then n = null
+                       if n == null or n.location.line_start != line  then
+                               # closes the line div
+                               html = stack2.pop
+
+                               # close the block production divs
+                               var c0 = c.last_token_in_line
+                               ending = null
+                               if c0 != null then ending = c0.ending_prods
+                               if ending != null then for p in ending do
+                                       if not p.is_block then continue
+                                       if stack.is_empty or p != stack.last then continue
+                                       stack.pop
+                                       html = stack2.pop
+                               end
+                       end
+
+                       c = n
+               end
+               if not stack2.is_empty then html = stack2.first
+       end
+
+       # Return a default CSS content related to CSS classes used in the `html` tree.
+       # Could be inlined in the `.html` file of saved as a specific `.css` file.
+       fun css_content: String
+       do
+               return """
+.nitcode a { color: inherit; cursor:pointer; }
+.nitcode .titled:hover { text-decoration: underline; } /* underline titles */
+.nitcode .popupable:hover { text-decoration: underline; cursor:help; } /* underline titles */
+.nitcode .foldable { display: block } /* for block productions*/
+.nitcode .line{ display: block } /* for lines */
+.nitcode .line:hover{ background-color: #FFFFE0; } /* current line */
+.nitcode :target { background-color: #FFF3C2 } /* target highlight*/
+/* lexical raw tokens. independent of usage or semantic: */
+.nitcode .nc_c { color: gray; font-style: italic; } /* comment */
+.nitcode .nc_d { color: #3D8127; font-style: italic; } /* documentation comments */
+.nitcode .nc_k { font-weight: bold; } /* keyword */
+.nitcode .nc_o {} /* operator */
+.nitcode .nc_i {} /* standard identifier */
+.nitcode .nc_t { color: #445588; font-weight: bold; } /* type/class identifier */
+.nitcode .nc_a { color: #445588; font-style: italic; } /* old style attribute identifier */
+.nitcode .nc_l { color: #009999; } /* char and number literal */
+.nitcode .nc_s { color: #8F1546; } /* string literal */
+/* syntactic token usage. added because of their position in the AST */
+.nitcode .nc_ast { color: blue; } /* assert label */
+.nitcode .nc_la { color: blue; } /* break/continue label */
+.nitcode .nc_m { color: #445588; } /* module name */
+/* syntactic groups */
+.nitcode .nc_def { font-weight: bold; color: blue; } /* name used in a definition */
+  .nitcode .nc_def.nc_a { color: blue; } /* name used in a attribute definition */
+  .nitcode .nc_def.nc_t { color: blue; } /* name used in a class or vt definition */
+.nitcode .nc_ss { color: #9E6BEB; } /* superstrings */
+.nitcode .nc_cdef {} /* A whole class definition */
+.nitcode .nc_pdef {} /* A whole property definition */
+/* semantic token usage */
+.nitcode .nc_v { font-style: italic; } /* local variable or parameter */
+.nitcode .nc_vt { font-style: italic; } /* virtual type or formal type */
+
+.nitcode .nc_error { border: 1px red solid;} /* not used */
+.popover { max-width: 800px !important; }
+"""
+       end
+
+       # Additional content to inject in the <head> tag
+       # Note: does not include `css_content`; handle it yourself.
+       fun head_content: String
+       do
+               return """<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">\n"""
+       end
+
+       # Additional content to inject just before the closing </body> tag
+       fun foot_content: String
+       do
+               return """
+<script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
+<script src="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
+<script>$(".popupable").popover({html:true, placement:'top'})/*initialize bootstrap popover*/</script>"""
+       end
+
+       # Fully process `content` as a Nit source file.
+       #
+       # Set `print_errors = true` to print errors in the code to the console.
+       fun highlightcode(content: String, print_errors: nullable Bool): HLCode
+       do
+               # Prepare a stand-alone tool context
+               var tc = new ToolContext
+               tc.nit_dir = tc.locate_nit_dir # still use the common lib to have core
+               tc.keep_going = true # no exit, obviously
+               if print_errors != true then tc.opt_warn.value = -1 # no output
+
+               # Prepare an stand-alone model and model builder.
+               # Unfortunately, models are enclosing and append-only.
+               # There is no way (yet?) to have a shared module `core` with
+               # isolated and throwable user modules.
+               var model = new Model
+               var mb = new ModelBuilder(model, tc)
+
+               # Parse the code
+               var source = new SourceFile.from_string("", content + "\n")
+               var lexer = new Lexer(source)
+               var parser = new Parser(lexer)
+               var tree = parser.parse
+
+               var hlcode = new HLCode(self, content, source)
+
+               # Check syntax error
+               var eof = tree.n_eof
+               if eof isa AError then
+                       mb.error(eof, eof.message)
+                       highlight_source(source)
+                       return hlcode
+               end
+               var amodule = tree.n_base.as(not null)
+
+               # Load the AST as a module in the model
+               # Then process it
+               mb.load_rt_module(null, amodule, "")
+               mb.run_phases
+
+               # Highlight the processed module
+               highlight_node(amodule)
+               return hlcode
+       end
+end
+
+redef class HTMLTag
+       # Attach the infobox to the node by using BootStrap popover
+       fun attach_infobox(infobox: HInfoBox)
+       do
+               classes.add("popupable")
+               attrs["title"] = infobox.title
+               var href = infobox.href
+               if href != null then
+                       attrs["data-title"] = """<a href="{{{href}}}">{{{infobox.title}}}</a>"""
+               end
+               attrs["data-content"] = infobox.content.write_to_string
+               attrs["data-toggle"] = "popover"
+       end
+end
+
+
+# A generic information container that can be used to decorate AST entities
+class HInfoBox
+       # The visitor used for contextualisation, if needed
+       var visitor: HtmlightVisitor
+
+       # A short title for the AST element
+       var title: String
+
+       # The primary link where the entity points
+       # null if no link
+       var href: nullable String = null
+
+       # The content of the popuped infobox
+       var content = new HTMLTag("div")
+
+       # Append a new field in the popuped infobox
+       fun new_field(title: String): HTMLTag
+       do
+               content.open("b").text(title)
+               content.append(" ")
+               var res = content.open("span")
+               content.open("br")
+               return res
+       end
+
+       # Append a new dropdown in the popuped content
+       fun new_dropdown(title, text: String, text_is_html: nullable Bool): HTMLTag
+       do
+               content.add_raw_html """<div class="dropdown"> <a data-toggle="dropdown" href="#"><b>"""
+               content.append(title)
+               content.add_raw_html "</b> "
+               if text_is_html == true then
+                       content.add_raw_html(text)
+               else content.append(text)
+               content.add_raw_html """<span class="caret"></span></a>"""
+               var res = content.open("ul").add_class("dropdown-menu").attr("role", "menu").attr("aria-labelledby", "dLabel")
+               content.add_raw_html "</div>"
+               return res
+       end
+end
+
+##
+
+# Model entity or whatever that can produce an infobox
+interface HInfoBoxable
+       # An new infobox documenting the entity
+       fun infobox(v: HtmlightVisitor): HInfoBox is abstract
+end
+
+redef class MDoc
+       # Append an entry for the doc in the given infobox
+       fun fill_infobox(res: HInfoBox)
+       do
+               if content.length < 2 then
+                       res.new_field("doc").text(content.first)
+                       return
+               end
+               var c = res.new_dropdown("doc", content.first)
+               for x in content.iterator.skip_head(1) do
+                       c.append x
+                       c.add_raw_html "<br>"
+               end
+       end
+end
+
+redef class MEntity
+       super HInfoBoxable
+
+       # A HTML version of `to_s` with hyper-links.
+       #
+       # By default, `linkto_text(v, to_s)` is used, c.f. see `linkto_text`.
+       #
+       # For some complex entities, like generic types, multiple `<a>` and `<span>` elements can be generated.
+       # E.g. `Array[Int]` might become `<a>Array</a>[<a>Int</a>]` with the correct `href` attributes
+       # provided  by `v.hrefto`.
+       fun linkto(v: HtmlightVisitor): HTMLTag do return linkto_text(v, to_s)
+
+       # Link to the `self` with a specific text.
+       #
+       # The whole text is linked with a single `<a>` element.
+       #
+       # The `href` used is provided by `v.hrefto`.
+       # If `href` is null then a `<span>` element is used instead of `<a>`.
+       fun linkto_text(v: HtmlightVisitor, text: String): HTMLTag
+       do
+               var href = v.hrefto(self)
+               if href == null then
+                       return (new HTMLTag("span")).text(text)
+               end
+               return (new HTMLTag("a")).attr("href", href).text(text)
+       end
+
+       # Append an entry for the doc in the given infobox
+       private fun add_doc_to_infobox(res: HInfoBox)
+       do
+               var mdoc = mdoc_or_fallback
+               if mdoc != null then mdoc.fill_infobox(res)
+       end
+end
+
+redef class MModule
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, "module {name}")
+               res.href = v.hrefto(self)
+               res.new_field("module").add(linkto(v))
+               add_doc_to_infobox(res)
+               if in_importation.greaters.length > 1 then
+                       var c = res.new_dropdown("imports", "{in_importation.greaters.length-1} modules")
+                       for x in in_importation.greaters do
+                               if x == self then continue
+                               c.open("li").add x.linkto(v)
+                       end
+               end
+               return res
+       end
+
+       redef fun linkto(v) do return linkto_text(v, name)
+end
+
+redef class MClassDef
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, "class {mclass.name}")
+               res.href = v.hrefto(self)
+               if is_intro then
+                       res.new_field("class").text(mclass.name)
+               else
+                       res.new_field("redef class").text(mclass.name)
+                       res.new_field("intro").add mclass.intro.linkto_text(v, "in {mclass.intro_mmodule.to_s}")
+               end
+               add_doc_to_infobox(res)
+
+               var in_hierarchy = self.in_hierarchy
+               if in_hierarchy == null then return res
+
+               if in_hierarchy.greaters.length > 1 then
+                       var c = res.new_dropdown("hier", "super-classes")
+                       for x in in_hierarchy.greaters do
+                               if x == self then continue
+                               if not x.is_intro then continue
+                               c.open("li").add x.linkto(v)
+                       end
+               end
+               if in_hierarchy.smallers.length > 1 then
+                       var c = res.new_dropdown("hier", "sub-classes")
+                       for x in in_hierarchy.smallers do
+                               if x == self then continue
+                               if not x.is_intro then continue
+                               c.open("li").add x.linkto(v)
+                       end
+               end
+               if mclass.mclassdefs.length > 1 then
+                       var c = res.new_dropdown("redefs", "refinements")
+                       for x in mclass.mclassdefs do
+                               if x == self then continue
+                               c.open("li").add x.linkto_text(v, "in {x.mmodule}")
+                       end
+               end
+               return res
+       end
+end
+
+redef class MPropDef
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               res.href = v.hrefto(self)
+               if self isa MMethodDef then
+                       var msignature = self.msignature
+                       if msignature != null then res.new_field("fun").append(mproperty.name).add msignature.linkto(v)
+               else if self isa MAttributeDef then
+                       var static_mtype = self.static_mtype
+                       if static_mtype != null then res.new_field("fun").append(mproperty.name).add static_mtype.linkto(v)
+               else if self isa MVirtualTypeDef then
+                       var bound = self.bound
+                       if bound != null then res.new_field("add").append(mproperty.name).add bound.linkto(v)
+               else
+                       res.new_field("wat?").append(mproperty.name)
+               end
+
+               if is_intro then
+               else
+                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
+               end
+               add_doc_to_infobox(res)
+               if mproperty.mpropdefs.length > 1 then
+                       var c = res.new_dropdown("redef", "redefinitions")
+                       for x in mproperty.mpropdefs do
+                               c.open("li").add x.linkto_text(v, "in {x.mclassdef}")
+                       end
+               end
+
+               return res
+       end
+end
+
+redef class MClassType
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               res.href = v.hrefto(self)
+               res.new_field("class").add mclass.intro.linkto(v)
+               add_doc_to_infobox(res)
+               return res
+       end
+       redef fun linkto(v)
+       do
+               return mclass.intro.linkto(v)
+       end
+end
+redef class MVirtualType
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               res.href = v.hrefto(mproperty)
+               var p = mproperty
+               res.new_field("virtual type").add p.intro.linkto(v)
+               add_doc_to_infobox(res)
+               return res
+       end
+       redef fun linkto(v)
+       do
+               return mproperty.intro.linkto(v)
+       end
+end
+redef class MParameterType
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               res.new_field("parameter type").append("{name} from class ").add mclass.intro.linkto(v)
+               return res
+       end
+end
+
+redef class MNullableType
+       redef fun infobox(v)
+       do
+               return mtype.infobox(v)
+       end
+       redef fun linkto(v)
+       do
+               var res = new HTMLTag("span")
+               res.append("nullable ").add(mtype.linkto(v))
+               return res
+       end
+end
+
+redef class MNotNullType
+       redef fun infobox(v)
+       do
+               return mtype.infobox(v)
+       end
+       redef fun linkto(v)
+       do
+               var res = new HTMLTag("span")
+               res.append("not null ").add(mtype.linkto(v))
+               return res
+       end
+end
+
+redef class MNullType
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, to_s)
+               return res
+       end
+       redef fun linkto(v)
+       do
+               var res = new HTMLTag("span")
+               res.append("null")
+               return res
+       end
+end
+
+redef class MSignature
+       redef fun linkto(v)
+       do
+               var res = new HTMLTag("span")
+               var first = true
+               if not mparameters.is_empty then
+                       res.append "("
+                       for p in mparameters do
+                               if first then
+                                       first = false
+                               else
+                                       res.append ", "
+                               end
+                               res.append p.name
+                               res.append ": "
+                               res.add p.mtype.linkto(v)
+                       end
+                       res.append ")"
+               end
+               var ret = return_mtype
+               if ret != null then
+                       res.append ": "
+                       res.add ret.linkto(v)
+               end
+               return res
+       end
+end
+
+redef class CallSite
+       redef fun infobox(v)
+       do
+               var res = new HInfoBox(v, "call {mpropdef}")
+               res.href = v.hrefto(mpropdef)
+               res.new_field("call").add(mpropdef.linkto(v)).add(msignature.linkto(v))
+               if mpropdef.is_intro then
+               else
+                       res.new_field("intro").add mproperty.intro.linkto_text(v, "in {mproperty.intro.mclassdef}")
+               end
+               add_doc_to_infobox(res)
+
+               return res
+       end
+       redef fun linkto(v)
+       do
+               return mpropdef.linkto(v)
+       end
+end
+
+redef class Variable
+       super HInfoBoxable
+       redef fun infobox(v)
+       do
+               var declared_type = self.declared_type
+               if declared_type == null then
+                       var res = new HInfoBox(v, "{name}")
+                       res.new_field("local var").append("{name}")
+                       return res
+               end
+               var res = new HInfoBox(v, "{name}: {declared_type}")
+               res.new_field("local var").append("{name}:").add(declared_type.linkto(v))
+               return res
+       end
+end
+
+
+##
+
+redef class ANode
+       # Optionally creates a tag that encapsulate the AST element on HTML rendering
+       protected fun make_tag(v: HtmlightVisitor): nullable HTMLTag do return null
+
+       # Add aditionnal information on a child-token and return an additionnal HInfoBox on it
+       protected fun decorate_tag(v: HtmlightVisitor, res: HTMLTag, token: Token): nullable HInfoBox
+       do
+               #debug("no decoration for {token.inspect}")
+               #res.add_class("nc_error")
+               return null
+       end
+
+       # Return a optional infobox
+       fun infobox(v: HtmlightVisitor): nullable HInfoBox do return null
+end
+
+redef class AQclassid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var parent = self.parent
+               if parent == null then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
+redef class AQid
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var parent = self.parent
+               if parent == null then return null
+               return parent.decorate_tag(v, res, token)
+       end
+end
+
+redef class AStdClassdef
+       redef fun make_tag(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_cdef")
+               var md = mclassdef
+               if md != null then res.attr("id", md.to_s)
+               return res
+       end
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TClassid then return null
+               res.add_class("nc_def")
+
+               var md = mclassdef
+               if md == null then return null
+               return md.infobox(v)
+       end
+end
+redef class APropdef
+       redef fun make_tag(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_pdef")
+               var mpd
+               mpd = mpropdef
+               if mpd != null then
+                       #res.add(tag(mpd))
+                       res.attr("id", mpd.to_s)
+               end
+               if self isa AAttrPropdef then
+                       mpd = mreadpropdef
+                       if mpd != null then res.add(tag(mpd))
+                       mpd = mwritepropdef
+                       if mpd != null then res.add(tag(mpd))
+               end
+               return res
+       end
+
+       private fun tag(mpd: MPropDef): HTMLTag
+       do
+               var a = new HTMLTag("a")
+               a.attr("id", mpd.to_s)
+               return a
+       end
+end
+
+redef class Token
+       # Produce an HTMLTag with the correct contents and CSS classes
+       # Subclasses can redefine it to decorate the tag
+       redef fun make_tag(v): HTMLTag
+       do
+               var res = new HTMLTag("span")
+               res.text(text)
+               return res
+       end
+end
+
+redef class TokenKeyword
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_k")
+               return res
+       end
+end
+redef class TokenOperator
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_o")
+               return res
+       end
+end
+
+redef class AVarFormExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var variable = self.variable
+               if variable == null then return null
+               res.add_class("nc_v")
+               return variable.infobox(v)
+       end
+end
+
+redef class AVardeclExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var variable = self.variable
+               if variable == null then return null
+               res.add_class("nc_v")
+               return variable.infobox(v)
+       end
+end
+
+redef class AForGroup
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               var vs = variables
+               if vs == null then return null
+               res.add_class("nc_v")
+               var idx = n_ids.index_of(token)
+               var variable = vs[idx]
+               return variable.infobox(v)
+       end
+end
+
+redef class AParam
+       redef fun decorate_tag(v, res, token)
+       do
+               if token != n_id then return null
+               var mp = mparameter
+               if mp == null then return null
+               var variable = self.variable
+               if variable == null then return null
+               res.add_class("nc_v")
+               return variable.infobox(v)
+       end
+end
+
+redef class AAssertExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               res.add_class("nc_ast")
+               return null
+       end
+end
+
+redef class ALabel
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               res.add_class("nc_la")
+               return null
+       end
+end
+
+redef class ASendExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               var callsite = self.callsite
+               if callsite == null then return null
+               return callsite.infobox(v)
+       end
+end
+
+redef class ANewExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               var callsite = self.callsite
+               if callsite == null then return null
+               return callsite.infobox(v)
+       end
+end
+
+redef class AAssignOp
+       redef fun decorate_tag(v, res, token)
+       do
+               var p = parent
+               assert p isa AReassignFormExpr
+
+               var callsite = p.reassign_callsite
+               if callsite == null then return null
+               return callsite.infobox(v)
+       end
+end
+
+redef class AModuleName
+       redef fun decorate_tag(v, res, token)
+       do
+               var p = parent
+               if p == null then return null
+               return p.decorate_tag(v, res, token)
+       end
+end
+
+redef class AModuledecl
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               res.add_class("nc_def")
+               res.add_class("nc_m")
+               var p = parent
+               assert p isa AModule
+               var mm = p.mmodule
+               if mm == null then return null
+               return mm.infobox(v)
+       end
+end
+
+redef class AStdImport
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               res.add_class("nc_m")
+               var mm = mmodule
+               if mm == null then return null
+               return mm.infobox(v)
+       end
+end
+redef class AAttrPropdef
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TId then return null
+               res.add_class("nc_def")
+               var mpd: nullable MPropDef
+               mpd = mreadpropdef
+               if mpd == null then mpd = mpropdef
+               if mpd == null then return null
+               return mpd.infobox(v)
+       end
+end
+
+redef class TId
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_i")
+               return res
+       end
+end
+redef class AMethid
+       redef fun make_tag(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_def")
+               return res
+       end
+       redef fun decorate_tag(v, res, token)
+       do
+               return null
+               # nothing to decorate
+       end
+       redef fun infobox(v)
+       do
+               var p = parent
+               if not p isa AMethPropdef then return null
+               var mpd = p.mpropdef
+               if mpd == null then return null
+               return mpd.infobox(v)
+       end
+end
+redef class TAttrid
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_a")
+               return res
+       end
+end
+redef class AAttrFormExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TAttrid then return null
+               var p = mproperty
+               if p == null then return null
+               return p.intro.infobox(v)
+       end
+end
+redef class TClassid
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_t")
+               return res
+       end
+end
+redef class AType
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TClassid then return null
+               var mt = mtype
+               if mt == null then return null
+               mt = mt.undecorate
+               if mt isa MFormalType then
+                       res.add_class("nc_vt")
+               end
+               return mt.infobox(v)
+       end
+end
+redef class AFormaldef
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TClassid then return null
+               res.add_class("nc_vt")
+               var mtype = self.mtype
+               if mtype == null then return null
+               return mtype.infobox(v)
+       end
+end
+redef class ATypePropdef
+       redef fun decorate_tag(v, res, token)
+       do
+               if not token isa TClassid then return null
+               res.add_class("nc_def")
+               var md = mpropdef
+               if md == null then return null
+               return md.infobox(v)
+       end
+end
+redef class TComment
+       redef fun make_tag(v)
+       do
+               var res = super
+               if is_loose then
+                       res.add_class("nc_c")
+               end
+               return res
+       end
+end
+redef class ADoc
+       redef fun make_tag(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_d")
+               return res
+       end
+end
+redef class TokenLiteral
+       redef fun make_tag(v)
+       do
+               var res = super
+               res.add_class("nc_l")
+               return res
+       end
+end
+redef class ASuperstringExpr
+       redef fun make_tag(v)
+       do
+               var res = new HTMLTag("span")
+               res.add_class("nc_ss")
+               return res
+       end
+end
+redef class AStringFormExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               # Workaround to tag strings
+               res.classes.remove("nc_l")
+               res.add_class("nc_s")
+               return super
+       end
+end
+redef class AExpr
+       redef fun decorate_tag(v, res, token)
+       do
+               var t = mtype
+               if t == null then return null
+               return t.infobox(v)
+       end
+end
index 2c66ce7..c76665e 100644 (file)
 # Tool that produces highlighting for Nit programs
 module nitlight
 
-import highlight
+import htmlight
 
 class NitlightVisitor
-       super HighlightVisitor
+       super HtmlightVisitor
 
        # The current highlight module
        #
@@ -94,7 +94,8 @@ var opt_last_line = new OptionInt("End the source file at this line (default: to
 var opt_dir = new OptionString("Output html files in a specific directory (required if more than one module)", "-d", "--dir")
 var opt_full = new OptionBool("Process also imported modules", "--full")
 var opt_ast = new OptionBool("Generate specific HTML elements for each Node of the AST", "--ast")
-toolcontext.option_context.add_option(opt_fragment, opt_line_id_prefix, opt_first_line, opt_last_line, opt_dir, opt_full)
+var opt_txt = new OptionBool("Generate text with ANSI coloring escape sequences", "--txt")
+toolcontext.option_context.add_option(opt_fragment, opt_line_id_prefix, opt_first_line, opt_last_line, opt_dir, opt_full, opt_ast, opt_txt)
 toolcontext.tooldescription = "Usage: nitlight [OPTION]... <file.nit>...\nGenerates HTML of highlited code from Nit source files."
 toolcontext.process_options(args)
 
@@ -116,6 +117,29 @@ else if mmodules.length > 1 then
        return
 end
 
+if opt_txt.value then
+       for mm in mmodules do
+               var v = new AnsiHighlightVisitor
+               v.include_loose_tokens = true
+               v.include_whole_lines = true
+
+               if opt_first_line.value != 0 then v.first_line = opt_first_line.value
+               if opt_last_line.value != 0 then v.last_line = opt_last_line.value
+               var m = modelbuilder.mmodule2node(mm)
+               assert m != null
+
+               v.highlight_node(m)
+               var page = v.result
+
+               if dir != null then
+                       page.write_to_file("{dir}/{mm.c_name}.txt")
+               else
+                       page.write_to(stdout)
+               end
+       end
+       return
+end
+
 for mm in mmodules do
        if dir != null then toolcontext.info("write {dir}/{mm.c_name}.html", 1)
 
@@ -150,7 +174,7 @@ for mm in mmodules do
                page.add_raw_html v.head_content
                page.add_raw_html "</head><body><pre class='nit_code'>"
        end
-       v.enter_visit(m)
+       v.highlight_node(m)
        if not opt_fragment.value then
                page.add(v.html)
                page.add_raw_html "</pre>"
@@ -186,7 +210,7 @@ if dir != null then
        page.add_raw_html "</li></body>"
        page.write_to_file("{dir}/index.html")
 
-       var v = new HighlightVisitor
+       var v = new HtmlightVisitor
        toolcontext.info("write {dir}/style.css", 1)
        var f = new FileWriter.open("{dir}/style.css")
        f.write v.css_content
index f41f9a4..98ac187 100644 (file)
@@ -91,11 +91,7 @@ class AndroidToolchain
                # ---
 
                var app_name = project.name
-               if not release then app_name += " Debug"
-
                var app_package = project.namespace
-               if not release then app_package += "_debug"
-
                var app_version = project.version
 
                var app_min_api = project.min_api
@@ -303,7 +299,7 @@ set(lib_build_DIR ../libgc/outputs)
 file(MAKE_DIRECTORY ${lib_build_DIR})
 
 ## Config
-add_definitions("-DGC_PTHREADS")
+add_definitions("-DALL_INTERIOR_POINTERS -DGC_THREADS -DUSE_MMAP -DUSE_MUNMAP -DJAVA_FINALIZATION -DNO_EXECUTE_PERMISSION -DGC_DONT_REGISTER_MAIN_STATIC_DATA")
 set(enable_threads TRUE)
 set(CMAKE_USE_PTHREADS_INIT TRUE)
 
@@ -378,19 +374,41 @@ target_link_libraries(nit_app gc-lib
                # Generate AndroidManifest.xml
 
                # Is there an icon?
-               var resolutions = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"]
-               var icon_available = false
+               var resolutions = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi", "anydpi", "anydpi-v26"]
+               var icon_name = null
+               var has_round = false
+
                for res in resolutions do
-                       var path = project_root / "android/res/drawable-{res}/icon.png"
-                       if path.file_exists then
-                               icon_available = true
+                       # New style mipmap
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher_round.png".file_exists then
+                               has_round = true
+                       end
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher.png".file_exists then
+                               icon_name = "@mipmap/ic_launcher"
+                               break
+                       end
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher.xml".file_exists then
+                               icon_name = "@mipmap/ic_launcher"
                                break
                        end
                end
+               if icon_name == null then
+                       # Old style drawable-hdpi/icon.png
+                       for res in resolutions do
+                               var path = project_root / "android/res/drawable-{res}/icon.png"
+                               if path.file_exists then
+                                       icon_name = "@drawable/icon"
+                                       break
+                               end
+                       end
+               end
 
                var icon_declaration
-               if icon_available then
-                       icon_declaration = "android:icon=\"@drawable/icon\""
+               if icon_name != null then
+                       icon_declaration = "android:icon=\"{icon_name}\""
+                       if app_target_api >= 25 and has_round then
+                               icon_declaration += "\n\t\tandroid:roundIcon=\"@mipmap/ic_launcher_round\""
+                       end
                else icon_declaration = ""
 
                # TODO android:roundIcon
index 13384f6..399691b 100644 (file)
 # Program used to test the Nit highlighter
 module test_highlight
 
-import highlight
+import htmlight
 import test_phase
 
 class TestHighlightVisitor
-       super HighlightVisitor
+       super HtmlightVisitor
        redef fun hrefto(e) do
                return "#" + e.c_name
        end
@@ -41,7 +41,7 @@ do
                var n = mb.mpropdef2node(pd)
                if not n isa APropdef then continue
                var hl = new TestHighlightVisitor
-               hl.enter_visit(n)
+               hl.highlight_node(n)
                print "<h1 id=\"{pd.c_name}\">{pd.full_name}</h1>"
                printn "<pre><code>"
                hl.html.write_to(stdout)
@@ -69,7 +69,7 @@ class THLVisitor
                        seen.add cn
 
                        var hl = new TestHighlightVisitor
-                       hl.enter_visit(n)
+                       hl.highlight_node(n)
                        print "<h2>AST node: {cn} at {n.location}</h2>"
                        printn "<pre><code>"
                        hl.html.write_to(stdout)
index 84dcc8c..561f0af 100644 (file)
@@ -305,8 +305,8 @@ redef class CodeCommand
        private fun render_source(mentity: MEntity, modelbuilder: ModelBuilder): nullable HTMLTag do
                var node = modelbuilder.mentity2node(mentity)
                if node == null then return null
-               var hl = new HighlightVisitor
-               hl.enter_visit node
+               var hl = new HtmlightVisitor
+               hl.highlight_node node
                return hl.html
        end
 end
index 784d683..10f0694 100644 (file)
@@ -16,7 +16,7 @@
 module api_light
 
 import web_base
-import highlight
+import htmlight
 
 redef class APIRouter
        redef init do
@@ -30,8 +30,8 @@ class APILight
        super APIHandler
 
        redef fun post(req, res) do
-               var hl = new HighlightVisitor
-               var hlcode = hightlightcode(hl, req.body)
+               var hl = new HtmlightVisitor
+               var hlcode = hl.highlightcode(req.body)
                res.json(hlcode)
        end
 end
index b7c35c0..0de4806 100644 (file)
@@ -15,7 +15,7 @@
 module api_model
 
 import web_base
-import highlight
+import htmlight
 import uml
 import model::model_index
 
@@ -311,8 +311,8 @@ class APIEntityCode
        private fun render_source(mentity: MEntity): nullable HTMLTag do
                var node = config.modelbuilder.mentity2node(mentity)
                if node == null then return null
-               var hl = new HighlightVisitor
-               hl.enter_visit node
+               var hl = new HtmlightVisitor
+               hl.highlight_node node
                return hl.html
        end
 end
index 0e7de6a..4f02479 100644 (file)
@@ -1,3 +1,4 @@
 -f base_simple3.nit
 base_simple3.nit
 -f --line-id-prefix XYZ --first-line 38 --last-line 46 base_simple3.nit
+--txt base_simple3.nit
diff --git a/tests/sav/nitlight_args4.res b/tests/sav/nitlight_args4.res
new file mode 100644 (file)
index 0000000..9104b4b
--- /dev/null
@@ -0,0 +1,66 @@
+\e[34m# This file is part of NIT ( http://www.nitlanguage.org ).
+\e[m\e[34m#
+\e[m\e[34m# Copyright 2006-2008 Jean Privat <jean@pryen.org>
+\e[m\e[34m#
+\e[m\e[34m# Licensed under the Apache License, Version 2.0 (the "License");
+\e[m\e[34m# you may not use this file except in compliance with the License.
+\e[m\e[34m# You may obtain a copy of the License at
+\e[m\e[34m#
+\e[m\e[34m#     http://www.apache.org/licenses/LICENSE-2.0
+\e[m\e[34m#
+\e[m\e[34m# Unless required by applicable law or agreed to in writing, software
+\e[m\e[34m# distributed under the License is distributed on an "AS IS" BASIS,
+\e[m\e[34m# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+\e[m\e[34m# See the License for the specific language governing permissions and
+\e[m\e[34m# limitations under the License.
+\e[m
+\e[33mimport\e[m \e[33mend\e[m
+
+\e[33minterface\e[m \e[32mObject\e[m
+\e[33mend\e[m
+
+\e[33menum\e[m \e[32mBool\e[m
+\e[33mend\e[m
+
+\e[33menum\e[m \e[32mInt\e[m
+       \e[33mfun\e[m output \e[33mis\e[m intern
+\e[33mend\e[m
+
+\e[33mclass\e[m \e[32mA\e[m
+       \e[33minit\e[m \e[33mdo\e[m \e[31m5\e[m.output
+       \e[33mfun\e[m run \e[33mdo\e[m \e[31m6\e[m.output
+\e[33mend\e[m
+
+\e[33mclass\e[m \e[32mB\e[m
+       \e[33mvar\e[m val: \e[32mInt\e[m
+       \e[33minit\e[m(v: \e[32mInt\e[m)
+       \e[33mdo\e[m
+               \e[31m7\e[m.output
+               \e[33mself\e[m.val = v
+       \e[33mend\e[m
+       \e[33mfun\e[m run \e[33mdo\e[m val.output
+\e[33mend\e[m
+
+\e[33mclass\e[m \e[32mC\e[m
+       \e[33mvar\e[m val1: \e[32mInt\e[m
+       \e[33mvar\e[m val2: \e[32mInt\e[m = \e[31m10\e[m
+\e[33mend\e[m
+
+\e[33mfun\e[m foo \e[33mdo\e[m \e[31m2\e[m.output
+\e[33mfun\e[m bar(i: \e[32mInt\e[m) \e[33mdo\e[m i.output
+\e[33mfun\e[m baz: \e[32mInt\e[m \e[33mdo\e[m \e[33mreturn\e[m \e[31m4\e[m
+
+\e[31m1\e[m.output
+foo
+bar(\e[31m3\e[m)
+baz.output
+
+\e[33mvar\e[m a = \e[33mnew\e[m \e[32mA\e[m
+a.run
+
+\e[33mvar\e[m b = \e[33mnew\e[m \e[32mB\e[m(\e[31m8\e[m)
+b.run
+
+\e[33mvar\e[m c = \e[33mnew\e[m \e[32mC\e[m(\e[31m9\e[m)
+c.val1.output
+c.val2.output
index 9fb5305..57d863d 100644 (file)
@@ -2,13 +2,13 @@ Compilation des classes Java ...
 Initialisation de la JVM ...
 ---------------------Test 1----------------------
 From java, pushing premier
-From java, pushing deuxi?me
-From java, pushing troisi?me
+From java, pushing deuxième
+From java, pushing troisième
 From java, popping premier
 premier
-From java, popping deuxi?me
+From java, popping deuxième
 deuxième
-From java, popping troisi?me
+From java, popping troisième
 troisième
 --------------------Test 2---------------------
 true
index eb9cc1a..9587665 100755 (executable)
@@ -18,8 +18,8 @@
 # This shell script compile, run and verify Nit program files
 
 # Set lang do default to avoid failed tests because of locale
-export LANG=C
-export LC_ALL=C
+export LANG=C.UTF-8
+export LC_ALL=C.UTF-8
 export NIT_TESTING=true
 # Use the pid as a collision prevention
 export NIT_TESTING_ID=$$
@@ -708,7 +708,7 @@ END
                        if [ -f "$ff.write" ]; then
                                cat -- "$ff.write" >> "$ff.res"
                        elif [ -d "$ff.write" ]; then
-                               LANG=C /bin/ls -F "$ff.write" >> "$ff.res"
+                               /bin/ls -F "$ff.write" >> "$ff.res"
                        fi
                        cp -- "$ff.res"  "$ff.res2"
                        cat -- "$ff.cmp.err" "$ff.err" "$ff.res2" > "$ff.res"
@@ -750,7 +750,7 @@ END
                                        if [ -f "$fff.write" ]; then
                                                cat -- "$fff.write" >> "$fff.res"
                                        elif [ -d "$fff.write" ]; then
-                                               LANG=C /bin/ls -F -- "$fff.write" >> "$fff.res"
+                                               /bin/ls -F -- "$fff.write" >> "$fff.res"
                                        fi
                                        if [ -s "$fff.err" ]; then
                                                cp -- "$fff.res"  "$fff.res2"