Search things from the Model

ModelIndex allows you to index mentities then retrieve them by their name or full_name. It offers a set of find_* methods that can be used to match queries against entities name.

Because each use is different, ModelIndex only provide base raw search services. All of them return IndexMatches that can be ordered and filtered by the client.

Indexing mentities

Before searching something from the ModelIndex, you have to index it. Use the ModelIndex::index method to do that:

var index = new ModelIndex
for mentity in model.collect_mentities do
    index.index(mentity)
end

Search mentities

You can then run queries on the ModelIndex:

for res in index.find("Array").limit(10) do
   print res
end

Examples

Here some examples of how you can use the ModelIndex.

Smart type prediction

Use ModelIndex to implement a smart prediction system based on the typed prefix:

var index = new ModelIndex
for mentity in model.collect_mentities do
    # We don't really care about definitions
    if mentity isa MClassDef or mentity isa MPropDef then continue
    index.index(mentity)
end

var typed_prefix = "Arr"
for res in index.find_by_name_prefix(typed_prefix).
    uniq. # keep only the best ranked mentity
    limit(5). # limit to ten results
    sort(new VisibilityComparator, new NameComparator) do # order by visibility then name
   print res
end

Will output something like:

Array (1)
ArraySet (2)
ArrayMap (3)
ArrayCmp (4)
ArrayMapKeys (5)

Method autocompletion

Find methods from a class full_name:

var class_full_name = "core::Array"
for res in index.find_by_full_name_prefix("{class_full_name}::").
    uniq. # keep only the best ranked mentity
    sort(new VisibilityComparator). # put private in the bottom of the list
    limit(5). # limit to ten results
    sort(new FullNameComparator) do # order by lexicographic order
   print res
end

Will output something like:

* (2)
+ (1)
filled_with (5)
from (3)
with_items (4)

Name typo detection and suggestion

Detect unknown names and suggest corrections:

var name = "Zrray"

if index.find_by_name_prefix(name).is_empty then
    printn "`{name}` not found, did you mean: "
    printn index.find_by_name_similarity(name).sort(new ScoreComparator).limit(2).join(" or ")
    print "?"
end

Will output something like:

`Zrray` not found, did you mean: Array (1) or array (1)?

Introduced classes

class FullNameComparator

nitc :: FullNameComparator

Compare two matches by their full_name in lexicographic order
class FullNameLengthComparator

nitc :: FullNameLengthComparator

Compare two matches by their full name length
class IndexMatch

nitc :: IndexMatch

An MEntity matched from a ModelIndex
class IndexMatches

nitc :: IndexMatches

An array of IndexMatch instances returned by the ModelIndex
class MEntityComparator

nitc :: MEntityComparator

Compare two matches by their MEntity kind
private class MatchComparators

nitc :: MatchComparators

A pipeline of comparators executed in inclusion order
class ModelIndex

nitc :: ModelIndex

ModelIndex indexes mentities by their name and full name
class NameComparator

nitc :: NameComparator

Compare two matches by their name in lexicographic order
class NameLengthComparator

nitc :: NameLengthComparator

Compare two matches by their name length
class ScoreComparator

nitc :: ScoreComparator

Compare two matches by their score
class VisibilityComparator

nitc :: VisibilityComparator

Compare two matches by their MEntity visibility

Redefined classes

redef class MClass

nitc :: model_index $ MClass

A named class
redef class MClassDef

nitc :: model_index $ MClassDef

A definition (an introduction or a refinement) of a class in a module
redef abstract class MEntity

nitc :: model_index $ MEntity

A named and possibly documented entity in the model.
redef class MGroup

nitc :: model_index $ MGroup

A group of modules in a package
redef class MModule

nitc :: model_index $ MModule

A Nit module is usually associated with a Nit source file.
redef class MPackage

nitc :: model_index $ MPackage

A Nit package, that encompass a product
redef abstract class MPropDef

nitc :: model_index $ MPropDef

A definition of a property (local property)
redef abstract class MProperty

nitc :: model_index $ MProperty

A service (global property) that generalize method, attribute, etc.
redef class Model

nitc :: model_index $ Model

The container class of a Nit object-oriented model.

All class definitions

class FullNameComparator

nitc $ FullNameComparator

Compare two matches by their full_name in lexicographic order
class FullNameLengthComparator

nitc $ FullNameLengthComparator

Compare two matches by their full name length
class IndexMatch

nitc $ IndexMatch

An MEntity matched from a ModelIndex
class IndexMatches

nitc $ IndexMatches

An array of IndexMatch instances returned by the ModelIndex
redef class MClass

nitc :: model_index $ MClass

A named class
redef class MClassDef

nitc :: model_index $ MClassDef

A definition (an introduction or a refinement) of a class in a module
redef abstract class MEntity

nitc :: model_index $ MEntity

A named and possibly documented entity in the model.
class MEntityComparator

nitc $ MEntityComparator

Compare two matches by their MEntity kind
redef class MGroup

nitc :: model_index $ MGroup

A group of modules in a package
redef class MModule

nitc :: model_index $ MModule

A Nit module is usually associated with a Nit source file.
redef class MPackage

nitc :: model_index $ MPackage

A Nit package, that encompass a product
redef abstract class MPropDef

nitc :: model_index $ MPropDef

A definition of a property (local property)
redef abstract class MProperty

nitc :: model_index $ MProperty

A service (global property) that generalize method, attribute, etc.
private class MatchComparators

nitc $ MatchComparators

A pipeline of comparators executed in inclusion order
redef class Model

nitc :: model_index $ Model

The container class of a Nit object-oriented model.
class ModelIndex

nitc $ ModelIndex

ModelIndex indexes mentities by their name and full name
class NameComparator

nitc $ NameComparator

Compare two matches by their name in lexicographic order
class NameLengthComparator

nitc $ NameLengthComparator

Compare two matches by their name length
class ScoreComparator

nitc $ ScoreComparator

Compare two matches by their score
class VisibilityComparator

nitc $ VisibilityComparator

Compare two matches by their MEntity visibility
package_diagram nitc::model_index model_index nitc::model_collect model_collect nitc::model_index->nitc::model_collect trees::trie trie nitc::model_index->trees::trie trees::bktree bktree nitc::model_index->trees::bktree nitc::model_filters model_filters nitc::model_collect->nitc::model_filters trees trees trees::trie->trees trees::abstract_tree abstract_tree trees::bktree->trees::abstract_tree ...nitc::model_filters ... ...nitc::model_filters->nitc::model_filters ...trees ... ...trees->trees ...trees::abstract_tree ... ...trees::abstract_tree->trees::abstract_tree nitc::commands_base commands_base nitc::commands_base->nitc::model_index nitc::test_model_index test_model_index nitc::test_model_index->nitc::model_index nitc::commands_model commands_model nitc::commands_model->nitc::commands_base nitc::commands_model... ... nitc::commands_model...->nitc::commands_model a_star-m a_star-m a_star-m->nitc::test_model_index a_star-m... ... a_star-m...->a_star-m

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module abstract_tree

trees :: abstract_tree

Introduce tree structures abstraction
module annotation

nitc :: annotation

Management and utilities on annotations
module array

core :: array

This module introduces the standard array structure.
module bintree

trees :: bintree

Binary Tree data-structure
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module digraph

graph :: digraph

Implementation of directed graphs, also called digraphs.
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module ini

ini :: ini

Read and write INI configuration files
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module lexer

nitc :: lexer

Lexer and its tokens.
module lexer_work

nitc :: lexer_work

Internal algorithm and data structures for the Nit lexer
module list

core :: list

This module handle double linked lists
module literal

nitc :: literal

Parsing of literal values in the abstract syntax tree.
module loader

nitc :: loader

Loading of Nit source files
module location

nitc :: location

Nit source-file and locations in source-file
module math

core :: math

Mathematical operations
module mdoc

nitc :: mdoc

Documentation of model entities
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module mmodule

nitc :: mmodule

modules and module hierarchies in the metamodel
module mmodule_data

nitc :: mmodule_data

Define and retrieve data in modules
module model

nitc :: model

Classes, types and properties
module model_base

nitc :: model_base

The abstract concept of model and related common things
module model_examples

nitc :: model_examples

Examples for Model entities
module modelbuilder_base

nitc :: modelbuilder_base

Load nit source files and build the associated model
module modelize_class

nitc :: modelize_class

Analysis and verification of class definitions to instantiate model element
module modelize_property

nitc :: modelize_property

Analysis and verification of property definitions to instantiate model element
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module mpackage

nitc :: mpackage

Modelisation of a Nit package
module native

core :: native

Native structures for text and bytes
module nitpm_shared

nitc :: nitpm_shared

Services related to the Nit package manager
module numeric

core :: numeric

Advanced services for Numeric types
module opts

opts :: opts

Management of options on the command line
module ordered_tree

ordered_tree :: ordered_tree

Manipulation and presentation of ordered trees.
module parse_annotations

nitc :: parse_annotations

Simple annotation parsing
module parser

nitc :: parser

Parser.
module parser_nodes

nitc :: parser_nodes

AST nodes of the Nit language
module parser_prod

nitc :: parser_prod

Production AST nodes full definition.
module parser_work

nitc :: parser_work

Internal algorithm and data structures for the Nit parser
module phase

nitc :: phase

Phases of the processing of nit programs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module rbtree

trees :: rbtree

A red–black tree is a data structure which is a type of self-balancing binary search tree.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module tables

nitc :: tables

Module that interfaces the parsing tables.
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module toolcontext

nitc :: toolcontext

Common command-line tool infrastructure than handle options and error messages
module trees

trees :: trees

General module for tree data structures
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module version

nitc :: version

This file was generated by git-gen-version.sh

Parents

module bktree

trees :: bktree

Implementation of BKTree
module model_collect

nitc :: model_collect

Collect things from the model.
module trie

trees :: trie

A trie (or prefix tree) is a datastructure used to perform prefix searches.

Children

module commands_base

nitc :: commands_base

Documentation commands

Descendants

module a_star-m

a_star-m

module api

nitc :: api

Components required to build a web server about the nit model.
module api_auth

nitc :: api_auth

module api_base

nitc :: api_base

Base classes used by nitweb.
module api_docdown

nitc :: api_docdown

Nitdoc specific Markdown format handling for Nitweb
module api_feedback

nitc :: api_feedback

Feedback related features
module api_light

nitc :: api_light

Highlight and collect messages from a piece of code
module api_model

nitc :: api_model

module commands_catalog

nitc :: commands_catalog

Commands to retrieve Catalog related data
module commands_docdown

nitc :: commands_docdown

Doc down related queries
module commands_graph

nitc :: commands_graph

Graph commands
module commands_http

nitc :: commands_http

Initialize commands from HTTP requests
module commands_model

nitc :: commands_model

Doc commands about a Model or a MEntity
module commands_parser

nitc :: commands_parser

A parser that create DocCommand from a string
module commands_usage

nitc :: commands_usage

Commands about how mentities are used
module html_commands

nitc :: html_commands

Render commands results as HTML
module json_commands

nitc :: json_commands

Translate command results to json
module md_commands

nitc :: md_commands

Render commands results as Markdown
module nitdoc

nitc :: nitdoc

Generator of static API documentation for the Nit language
module nitpackage

nitc :: nitpackage

Helpful features about packages
module nitweb

nitc :: nitweb

Runs a webserver based on nitcorn that render things from model.
module nitx

nitc :: nitx

nitx, a command tool that displays useful data about Nit code
module static

nitc :: static

Nitdoc generation framework
module static_base

nitc :: static_base

Base entities shared by all the nitdoc code
module static_cards

nitc :: static_cards

Cards templates for the static documentation
module static_html

nitc :: static_html

Render documentation pages as HTML
module static_index

nitc :: static_index

Manage indexing of Nit model for Nitdoc QuickSearch.
module static_structure

nitc :: static_structure

Composes the pages of the static documentation
module term

nitc :: term

# Search things from the Model
#
# ModelIndex allows you to index mentities then retrieve them by their `name`
# or `full_name`.
# It offers a set of `find_*` methods that can be used to match queries
# against entities name.
#
# Because each use is different, ModelIndex only provide base raw search services.
# All of them return IndexMatches that can be ordered and filtered by the client.
#
# ## Indexing mentities
#
# Before searching something from the ModelIndex, you have to index it.
# Use the `ModelIndex::index` method to do that:
#
# ~~~nitish
# var index = new ModelIndex
# for mentity in model.collect_mentities do
#	index.index(mentity)
# end
# ~~~
#
# ## Search mentities
#
# You can then run queries on the ModelIndex:
#
# ~~~nitish
# for res in index.find("Array").limit(10) do
#    print res
# end
# ~~~
#
# ## Examples
#
# Here some examples of how you can use the ModelIndex.
#
# ### Smart type prediction
#
# Use ModelIndex to implement a smart prediction system based on the typed prefix:
#
# ~~~nitish
# var index = new ModelIndex
# for mentity in model.collect_mentities do
#	# We don't really care about definitions
#	if mentity isa MClassDef or mentity isa MPropDef then continue
#	index.index(mentity)
# end
#
# var typed_prefix = "Arr"
# for res in index.find_by_name_prefix(typed_prefix).
#	uniq. # keep only the best ranked mentity
#	limit(5). # limit to ten results
#	sort(new VisibilityComparator, new NameComparator) do # order by visibility then name
#    print res
# end
# ~~~
#
# Will output something like:
#
# ~~~raw
# Array (1)
# ArraySet (2)
# ArrayMap (3)
# ArrayCmp (4)
# ArrayMapKeys (5)
# ~~~
#
# ### Method autocompletion
#
# Find methods from a class full_name:
#
# ~~~nitish
# var class_full_name = "core::Array"
# for res in index.find_by_full_name_prefix("{class_full_name}::").
#	uniq. # keep only the best ranked mentity
#	sort(new VisibilityComparator). # put private in the bottom of the list
#	limit(5). # limit to ten results
#	sort(new FullNameComparator) do # order by lexicographic order
#    print res
# end
# ~~~
#
# Will output something like:
#
# ~~~raw
# * (2)
# + (1)
# filled_with (5)
# from (3)
# with_items (4)
# ~~~
#
# ### Name typo detection and suggestion
#
# Detect unknown names and suggest corrections:
#
# ~~~nitish
# var name = "Zrray"
#
# if index.find_by_name_prefix(name).is_empty then
#	printn "`{name}` not found, did you mean: "
#	printn index.find_by_name_similarity(name).sort(new ScoreComparator).limit(2).join(" or ")
#	print "?"
# end
# ~~~
#
# Will output something like:
#
# ~~~raw
# `Zrray` not found, did you mean: Array (1) or array (1)?
# ~~~
module model_index

import model::model_collect

import trees::trie
import trees::bktree

redef class Model

	# Keep a direct link to mentities by full name to speed up `mentity_from_uri`
	var mentities_by_full_name: HashMap[String, MEntity] is lazy do
		var mentities_by_full_name = new HashMap[String, MEntity]
		for mentity in collect_mentities do
			mentities_by_full_name[mentity.full_name] = mentity
		end
		return mentities_by_full_name
	end

	# ModelIndex used to perform searches
	var index: ModelIndex is lazy, writable do
		var index = new ModelIndex
		for mentity in collect_mentities do
			if mentity isa MClassDef or mentity isa MPropDef then continue
			index.index mentity
		end
		return index
	end

	redef fun mentities_by_name(name, filter) do
		var res = new Array[MEntity]
		if not index.names.has_key(name) then return res
		for mentity in index.names[name] do
			if filter == null or filter.accept_mentity(mentity) then res.add mentity
		end
		return res
	end

	redef fun mentity_by_full_name(full_name, filter) do
		if mentities_by_full_name.has_key(full_name) then
			var mentity = mentities_by_full_name[full_name]
			if filter == null or filter.accept_mentity(mentity) then return mentity
		end
		return null
	end

	private var score_sorter = new ScoreComparator
	private var vis_sorter = new VisibilityComparator
	private var kind_sorter = new MEntityComparator
	private var name_sorter = new NameComparator
	private var lname_sorter = new NameLengthComparator
	private var fname_sorter = new FullNameComparator
	private var lfname_sorter = new FullNameLengthComparator

	# Search mentities based on a `query` string
	#
	# Lookup the index for anything matching `query` and return `limit` results.
	#
	# The algorithm used is the following:
	# 1- lookup by name prefix
	# 2- lookup by full_name prefix
	# 3- loopup by levenshtein distance
	#
	# At each step if the `limit` is reached, the algorithm stops and returns the results.
	fun find(query: String, limit: nullable Int, filter: nullable ModelFilter): Array[MEntity] do
		# Find, lookup by name prefix
		var matches = index.find_by_name_prefix(query, filter).uniq.
			sort(lname_sorter, name_sorter, kind_sorter)
		if limit != null and matches.length >= limit then
			return matches.limit(limit).rerank.sort(vis_sorter, score_sorter).mentities
		end
		matches = matches.rerank.sort(vis_sorter, score_sorter)

		# If limit not reached, lookup by full_name prefix
		var malus = matches.length
		var full_matches = new IndexMatches
		for match in index.find_by_full_name_prefix(query, filter).
			sort(kind_sorter, lfname_sorter, fname_sorter) do
			match.score += malus
			full_matches.add match
		end
		matches = matches.uniq
		if limit != null and matches.length + full_matches.length >= limit then
			matches.add_all full_matches
			matches = matches.uniq.limit(limit).rerank.sort(vis_sorter, score_sorter)
			return matches.mentities
		end

		# If limit not reached, lookup by similarity
		malus = matches.length
		var sim_matches = new IndexMatches
		for match in index.find_by_similarity(query, filter).sort(score_sorter, kind_sorter, lname_sorter, name_sorter) do
			match.score += malus
			sim_matches.add match
		end
		matches.add_all sim_matches
		matches = matches.uniq
		if limit != null then matches = matches.limit(limit)
		return matches.rerank.sort(vis_sorter, score_sorter).mentities
	end
end

# ModelIndex indexes mentities by their name and full name
#
# It provides methods to find mentities based on a prefix or string similarity.
#
# ~~~nitish
# # Build index
# var index = new ModelIndex
# for mentity in model.collect_mentities do
#	if mentity isa MClassDef or mentity isa MPropDef then continue
#	index.index(mentity)
# end
#
# for e in index.find("Foo").uniq.sort(new ScoreComparator).limit(10) do
#	print " * {e.score}: {e.mentity.name} ({e.mentity.full_name})"
# end
# ~~~
class ModelIndex

	# List of all indexed mentities.
	#
	# Faster than traversing the tries.
	var mentities = new Array[MEntity]

	# Map of all mentities indexed by their `name`
	var names = new HashMap[String, Array[MEntity]]

	# Prefix tree for mentities `name`
	#
	# Because multiple mentities can share the same `name`, we use a Trie of
	# arrays of mentities.
	#
	# As for now, we do not index class and property definitions.
	# TODO add an option.
	var name_prefixes = new Trie[Array[MEntity]]

	# Distance tree for mentities `name`
	var name_distances = new BKTree

	# Map of all mentities indexed by their `full_name`
	var full_names = new HashMap[String, MEntity]

	# Prefix tree for mentities `full_name`
	#
	# Even if two mentities cannot share the same `full_name`, we use a Trie of
	# arrays of mentities to be consistent with `name_prefixes`.
	var full_name_prefixes = new Trie[Array[MEntity]]

	# Distance tree for mentities `full_name`
	var full_name_distances = new BKTree

	# Index `mentity` by it's `MEntity::name`
	#
	# See `name_prefixes`.
	private fun index_by_name(mentity: MEntity) do
		var name = mentity.name

		# Index name
		if not names.has_key(name) then
			names[name] = new Array[MEntity]
		end
		names[name].add mentity

		# Index prefix
		if not name_prefixes.has_key(name) then
			name_prefixes[name] = new Array[MEntity]
		end
		name_prefixes[name].add mentity

		# Index distance
		name_distances.add(name)
	end

	# Index `mentity` by its `MEntity::full_name`
	private fun index_by_full_name(mentity: MEntity) do
		var name = mentity.full_name

		# Index full name
		full_names[name] = mentity

		# Index prefix
		if not full_name_prefixes.has_key(name) then
			full_name_prefixes[name] = new Array[MEntity]
		end
		full_name_prefixes[name].add mentity

		# Index distance
		full_name_distances.add(name)
	end

	# Index `mentity` so it can be retrieved by a find query
	#
	# MEntities are indexed by both name and full_name.
	fun index(mentity: MEntity) do
		mentities.add mentity
		index_by_name mentity
		index_by_full_name mentity
	end

	# Translate Trie results to `SearchResult`
	#
	# This method is used internally to translate each mentity returned by a prefix
	# match in a Trie into a SearchResult that can be ranked by score.
	#
	# Results from the Trie are returned in a breadth first manner so we get the
	# matches ordered by prefix.
	# We preserve that order by giving an incremental score to the `array` items.
	private fun score_results_incremental(array: Array[Array[MEntity]], filter: nullable ModelFilter): IndexMatches do
		var results = new IndexMatches
		var score = 1
		for mentities in array do
			for mentity in mentities do
				if filter != null and not filter.accept_mentity(mentity) then continue
				results.add new IndexMatch(mentity, score)
			end
			score += 1
		end
		return results
	end

	# Find all mentities where `MEntity::name` matches the `prefix`
	fun find_by_name_prefix(prefix: String, filter: nullable ModelFilter): IndexMatches do
		return score_results_incremental(name_prefixes.find_by_prefix(prefix), filter)
	end

	# Find all mentities where `MEntity::full_name` matches the `prefix`
	fun find_by_full_name_prefix(prefix: String, filter: nullable ModelFilter): IndexMatches do
		return score_results_incremental(full_name_prefixes.find_by_prefix(prefix), filter)
	end

	# Rank all mentities by the distance between `MEntity::name` and `name`
	#
	# Use the Levenshtein algorithm on all the indexed mentities `name`.
	# Warning: may not scale to large indexes.
	fun find_by_name_similarity(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = new IndexMatches
		for match in name_distances.search(name) do
			var dist = match.distance
			var mname = match.key
			if not names.has_key(mname) then continue
			for mentity in names[mname] do
				if mentity isa MClassDef or mentity isa MPropDef then continue
				if filter != null and not filter.accept_mentity(mentity) then continue
				results.add new IndexMatch(mentity, dist)
			end
		end
		return results
	end

	# Rank all mentities by the distance between `MEntity::full_name` and `full_name`
	#
	# Use the Levenshtein algorithm on all the indexed mentities `full_name`.
	# Warning: may not scale to large indexes.
	fun find_by_full_name_similarity(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = new IndexMatches
		for match in full_name_distances.search(name) do
			var dist = match.distance
			var mname = match.key
			if not full_names.has_key(mname) then continue
			var mentity = full_names[mname]
			if mentity isa MClassDef or mentity isa MPropDef then continue
			if filter != null and not filter.accept_mentity(mentity) then continue
			results.add new IndexMatch(mentity, dist)
		end
		return results
	end

	# Rank all mentities by the distance between `name` and both the mentity name and full name
	fun find_by_similarity(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = new IndexMatches
		results.add_all find_by_full_name_similarity(name, filter)
		results.add_all find_by_name_similarity(name, filter)
		return results
	end

	# Find mentities by name trying first by prefix then by similarity
	fun find_by_name(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = find_by_name_prefix(name, filter)
		results.add_all find_by_name_similarity(name, filter)
		return results
	end

	# Find mentities by full name trying firt by prefix then by similarity
	fun find_by_full_name(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = find_by_full_name_prefix(name)
		results.add_all find_by_full_name_similarity(name, filter)
		return results
	end

	# Find all mentities that matches `name`
	#
	# 1. try by name prefix
	# 2. add full name prefix matches
	# 3. try similarity by name
	# 4. try similarity by full_name
	fun find(name: String, filter: nullable ModelFilter): IndexMatches do
		var results = find_by_name_prefix(name, filter)
		results.add_all find_by_full_name_prefix(name, filter)
		results.add_all find_by_similarity(name, filter)
		return results
	end
end

# An array of IndexMatch instances returned by the ModelIndex
#
# Index matches can be sorted, filtered and truncated.
#
# Thanks to the fluent interface, the index matches can be manipulated in chain
# from a model index query:
#
# ~~~nitish
# var res = index.find("Foo").
#   uniq.
#	sort(new ScoreComparator, new MEntityComparator).
#	limit(10).
#	sort(new VisibilityComparator)
# ~~~
class IndexMatches
	super Array[IndexMatch]

	# Create a new ModelMatches from an array of matches
	#
	# Elements are copied.
	init from_matches(matches: Array[IndexMatch]) do self.add_all matches

	# Sort the matches with `comparator` (or a list of comparators)
	#
	# Return a new IndexMatches instance with the sorted results.
	#
	# When more than one comparator is given, the comparators are applied in a
	# pipeline where the `n`th comparator is applied only if the `n-1`th comparator
	# returned 0.
	fun sort(comparator: ScoreComparator...): IndexMatches do
		var res = to_a
		if comparator.length == 1 then
			comparator.first.sort res
		else
			var comparators = new MatchComparators(comparator)
			comparators.sort res
		end
		return new IndexMatches.from_matches(res)
	end

	# Limit the matches with `limit`
	#
	# Return a new IndexMatches instance with only the `limit` first matches.
	fun limit(limit: Int): IndexMatches do
		var res = new Array[IndexMatch]
		for match in self do
			if res.length >= limit then break
			res.add match
		end
		return new IndexMatches.from_matches(res)
	end

	# Remove doublons from the matches
	#
	# Preverse the lowest score of all the matches for a MEntity.
	fun uniq: IndexMatches do
		var scores = new HashMap[MEntity, IndexMatch]
		var res = new Array[IndexMatch]
		for match in self do
			var mentity = match.mentity
			if scores.has_key(mentity) then
				var older = scores[mentity]
				if match.score < older.score then older.score = match.score
			else
				scores[mentity] = match
				res.add match
			end
		end
		return new IndexMatches.from_matches(res)
	end

	# Reset score of each matches to follow `self` order
	#
	# Usefull when you need to apply one sorter over another.
	fun rerank: IndexMatches do
		var res = new IndexMatches
		for match in self do
			res.add match
			match.score = res.length
		end
		return res
	end

	# Aggregate the mentities for all the matches
	#
	# Preserve the match order.
	fun mentities: Array[MEntity] do
		var res = new Array[MEntity]
		for match in self do res.add match.mentity
		return res
	end
end

# An MEntity matched from a ModelIndex
#
# Each match has a `score`. The score should be seen as the distance of
# the match from the query. In other words, the lowest is the score, the more
# relevant is the match.
class IndexMatch
	super Comparable

	redef type OTHER: IndexMatch

	# MEntity matches
	var mentity: MEntity

	# Score allocated by the search method
	#
	# A lowest score means a more relevant match.
	#
	# Scores values are arbitrary, the meaning of `10` vs `2000` really depends
	# on the search method producing the match and the comparators used to sort
	# the matches.
	# The only universal rule is: low score = relevance.
	var score: Int is writable

	# By default matches are compared only on their score
	redef fun <=>(o) do return score <=> o.score

	redef fun to_s do return "{mentity} ({score})"
end

# Compare two matches by their score
#
# Since the score can be seen as a distance, we want the lowest score first.
class ScoreComparator
	super Comparator

	redef type COMPARED: IndexMatch

	redef fun compare(o1, o2) do return o1.score <=> o2.score
end

# A pipeline of comparators executed in inclusion order
#
# This class is used internally to agregate the behaviors of multiple comparators.
# Use `IndexMatches::sort(comparator...)` instead.
private class MatchComparators
	super ScoreComparator

	# Comparator to use in the array order
	var comparators: Array[ScoreComparator]

	# Compare with each comparator
	#
	# Return the compare value of the first one to return anything else than 0.
	redef fun compare(o1, o2) do
		for comparator in comparators do
			var c = comparator.compare(o1, o2)
			if c != 0 then return c
		end
		return 0
	end
end

# Compare two matches by their MEntity kind
#
# Usefull to order the mentities by kind in this order:
# packages, groups, modules and classes, properties.
class MEntityComparator
	super ScoreComparator

	# See `MEntity::compare_mentity`
	redef fun compare(o1, o2) do
		return o1.mentity.mentity_kind_rank <=> o2.mentity.mentity_kind_rank
	end
end

# Compare two matches by their MEntity visibility
#
# We reverse the original order so private is at the end of the list.
class VisibilityComparator
	super ScoreComparator

	redef fun compare(o1, o2) do return o2.mentity.visibility <=> o1.mentity.visibility
end

# Compare two matches by their name in lexicographic order
#
# Generally, for a same score, we want to put A before Z.
class NameComparator
	super ScoreComparator

	redef fun compare(o1, o2) do return o1.mentity.name <=> o2.mentity.name
end

# Compare two matches by their name length
class NameLengthComparator
	super ScoreComparator

	redef fun compare(o1, o2) do return o1.mentity.name.length <=> o2.mentity.name.length
end

# Compare two matches by their full_name in lexicographic order
#
# Generally, for a same score, we want to put A before Z.
class FullNameComparator
	super ScoreComparator

	redef fun compare(o1, o2) do return o1.mentity.full_name <=> o2.mentity.full_name
end

# Compare two matches by their full name length
class FullNameLengthComparator
	super ScoreComparator

	redef fun compare(o1, o2) do return o1.mentity.full_name.length <=> o2.mentity.full_name.length
end

redef class MEntity

	# Compare MEntity class kind
	#
	# Unknown kind have a virtually high score.
	private fun mentity_kind_rank: Int do return 10
end

redef class MPackage
	redef fun mentity_kind_rank do return 1
end

redef class MGroup
	redef fun mentity_kind_rank do return 2
end

redef class MModule
	redef fun mentity_kind_rank do return 3
end

redef class MClass
	redef fun mentity_kind_rank do return 4
end

redef class MClassDef
	redef fun mentity_kind_rank do return 5
end

redef class MProperty
	redef fun mentity_kind_rank do return 6
end

redef class MPropDef
	redef fun mentity_kind_rank do return 7
end
src/model/model_index.nit:15,1--672,3