Muchos editores e IDE completan el código. Algunos de ellos son muy "inteligentes", otros no lo son realmente. Me interesa el tipo más inteligente. Por ejemplo, he visto IDE que solo ofrecen una función si a) está disponible en el alcance actual b) su valor de retorno es válido. (Por ejemplo, después de "5 + foo [tab]", solo ofrece funciones que devuelven algo que se puede agregar a un número entero o nombres de variable del tipo correcto). También he visto que colocan la opción más usada o más larga delante de la lista.
Me doy cuenta de que necesitas analizar el código. Pero normalmente, aunque la edición del código actual no es válida, hay errores de sintaxis en él. ¿Cómo se analiza algo cuando está incompleto y contiene errores?
También existe una limitación de tiempo. La finalización es inútil si se necesitan segundos para hacer una lista. A veces, el algoritmo de finalización se ocupa de miles de clases.
¿Cuáles son los buenos algoritmos y estructuras de datos para esto?
fuente
Respuestas:
El motor IntelliSense en mi producto de servicio de lenguaje UnrealScript es complicado, pero daré una descripción general lo mejor que pueda aquí. El servicio de lenguaje C # en VS2008 SP1 es mi objetivo de rendimiento (por una buena razón). Todavía no está ahí, pero es lo suficientemente rápido / preciso como para ofrecer sugerencias de forma segura después de escribir un solo carácter, sin esperar a que ctrl + espacio o que el usuario escriba un
.
(punto). Cuanta más información reciba la gente [que trabaja en servicios lingüísticos] sobre este tema, mejor será la experiencia del usuario final si alguna vez utilizo sus productos. Hay una serie de productos con los que he tenido la desafortunada experiencia de trabajar que no prestaron tanta atención a los detalles y, como resultado, estaba peleando con el IDE más de lo que estaba codificando.En mi servicio de idiomas, se presenta de la siguiente manera:
aa.bb.cc
, pero también puede contener llamadas a métodos como enaa.bb(3+2).cc
.IDeclarationProvider
, donde puede llamarGetDeclarations()
para obtener unoIEnumerable<IDeclaration>
de todos los elementos visibles en el alcance. En mi caso, esta lista contiene los parámetros locales (si están en un método), miembros (campos y métodos, solo estáticos a menos que estén en un método de instancia, y no miembros privados de tipos base), globales (tipos y constantes para el lenguaje que en el que estoy trabajando) y palabras clave. En esta lista habrá un elemento con el nombreaa
. Como primer paso para evaluar la expresión en el n. ° 1, seleccionamos el elemento de la enumeración de contexto con el nombreaa
, lo que nos da unIDeclaration
para el siguiente paso.IDeclaration
representaciónaa
para obtener otro queIEnumerable<IDeclaration>
contenga los "miembros" (en cierto sentido) deaa
. Dado que el.
operador es diferente del->
operador, llamodeclaration.GetMembers(".")
y espero que elIDeclaration
objeto aplique correctamente el operador enumerado.cc
, donde la lista de declaraciones puede contener o no un objeto con el nombrecc
. Como estoy seguro de que sabe, si varios elementos comienzan concc
, también deberían aparecer. Resuelvo esto tomando la enumeración final y pasándola a través de mi algoritmo documentado para proporcionar al usuario la información más útil posible.Aquí hay algunas notas adicionales para el backend de IntelliSense:
GetMembers
. Cada objeto en mi caché puede proporcionar un functor que evalúa a sus miembros, por lo que realizar acciones complicadas con el árbol es casi trivial.List<IDeclaration>
de sus miembros, mantengo unList<Name>
, dondeName
es una estructura que contiene el hash de una cadena con formato especial que describe el miembro. Hay un caché enorme que asigna nombres a objetos. De esta forma, cuando vuelvo a analizar un archivo, puedo eliminar todos los elementos declarados en el archivo del caché y volver a llenarlo con los miembros actualizados. Debido a la forma en que se configuran los functores, todas las expresiones se evalúan inmediatamente a los nuevos elementos.IntelliSense "interfaz"
A medida que el usuario escribe, el archivo es sintácticamente incorrecto con más frecuencia de lo que es correcto. Como tal, no quiero eliminar al azar secciones de la caché cuando el usuario escribe. Tengo una gran cantidad de reglas de casos especiales para manejar las actualizaciones incrementales lo más rápido posible. La caché incremental solo se mantiene local en un archivo abierto y ayuda a garantizar que el usuario no se dé cuenta de que su escritura está causando que la caché de backend contenga información de línea / columna incorrecta para cosas como cada método en el archivo.
Fragmento de código de la sección anterior:
Pensé que agregaría una lista de las características de IntelliSense que he implementado con este diseño. Las fotos de cada uno se encuentran aquí.
fuente
No puedo decir exactamente qué algoritmos se utilizan en una implementación en particular, pero puedo hacer algunas conjeturas. Un trie es una estructura de datos muy útil para este problema: el IDE puede mantener un gran trie en la memoria de todos los símbolos de su proyecto, con algunos metadatos adicionales en cada nodo.
Cuando escribe un carácter, recorre un camino en el trie. Todos los descendientes de un nodo trie particular son posibles completaciones. Luego, el IDE solo necesita filtrarlos por aquellos que tengan sentido en el contexto actual, pero solo necesita calcular tantos como se puedan mostrar en la ventana emergente de finalización de pestañas.
La finalización de pestañas más avanzada requiere un proceso más complicado. Por ejemplo, Visual Assist X tiene una función mediante la cual solo necesita escribir las letras mayúsculas de los símbolos de CamelCase; por ejemplo, si escribe SFN, le muestra el símbolo
SomeFunctionName
en su ventana de finalización de tabulación.Calcular el trie (u otras estructuras de datos) requiere analizar todo su código para obtener una lista de todos los símbolos en su proyecto. Visual Studio almacena esto en su base de datos IntelliSense, un
.ncb
archivo almacenado junto con su proyecto, para que no tenga que analizar todo cada vez que cierra y vuelve a abrir su proyecto. La primera vez que abre un proyecto grande (digamos, uno que acaba de sincronizar el control de fuente del formulario), VS se tomará el tiempo para analizar todo y generar la base de datos.No sé cómo maneja los cambios incrementales. Como dijiste, cuando escribes código, la sintaxis es inválida el 90% del tiempo, y analizar todo cada vez que estás inactivo supondría un gran impuesto sobre tu CPU por muy poco beneficio, especialmente si estás modificando un archivo de encabezado incluido por una gran cantidad de archivos fuente.
Sospecho que (a) solo repara cada vez que realmente construye su proyecto (o posiblemente cuando lo cierra / abre), o (b) realiza algún tipo de análisis local donde solo analiza el código donde acaba de editado de forma limitada, solo para obtener los nombres de los símbolos relevantes. Dado que C ++ tiene una gramática tan extraordinariamente complicada, puede comportarse de manera extraña en los rincones oscuros si está utilizando metaprogramación de plantilla pesada y similares.
fuente
El siguiente enlace le ayudará más ...
Resaltado de sintaxis: Cuadro de texto coloreado rápido para resaltado de sintaxis
fuente