首页 > 其他分享 >YouCompleteMe的completer和parser

YouCompleteMe的completer和parser

时间:2024-10-18 20:34:01浏览次数:8  
标签:completion semantic YouCompleteMe data self parser request completer

intro

在使用YCM完成c++输入提示(complete)时可以注意到一个细节:通常只有后输入“.”,"->","::"的时候提示的才是提示字段的类型信息。对于函数,提示包含了函数的参数类型等信息;对于数据成员,提示中也包含了类型信息。

对应地,其它情况下尽管提示中可能会包含变量名,但是不会在preview窗口中显示这些提示的类型信息。更让人觉得不方便的地方在于提示的内容通常都是只在当前文件中,如果此时不太记得某个函数(可能是第三方实现的接口),那么这个提示就会丢失不少珍贵的信息。

直观上看,YCM的后端cpp解析器是能够看到所有包含头文件展开之后的标识符,并且内置提供了转到声明的功能(YcmCompleter GoToDeclaration),所以找到整个编译单元(translation unit)中提示符的类型信息并非难事。

那么该如何实现呢?

completer

客户端

当vim客户端触发complete请求时,调用SendCompletionRequest接口发送请求,这个请求参数中包含了一个force_semantic参数。

#@file: YouCompleteMe/python/ycm/youcompleteme.py
def SendCompletionRequest( self, force_semantic = False ):
	request_data = BuildRequestData()
	request_data[ 'force_semantic' ] = force_semantic
	#....
	self._latest_completion_request = CompletionRequest( request_data )
	#....

接下来通过CompletionRequest接口向服务器发送"completions"请求,ycmd将通过这个URL标识符识别出来是一个complete请求。

#@file: YouCompleteMe/python/ycm/client/completion_request.py
class CompletionRequest( BaseRequest ):
def Start( self ):
	self._response_future = self.PostDataToHandlerAsync( self.request_data,
	'completions' )

ycmd

server在处理完成请求时,通过ShouldUseFiletypeCompleter接口判断是否使用基于有文件类型的completer,对C++来说,这个completer就是clangd。

如果没有文件类型定制的completer(GetFiletypeCompleter),则执行通用机制,也就是基于标识符的completer(GetGeneralCompleter)。

#@file: YouCompleteMe/third_party/ycmd/ycmd/handlers.py
@app.post( '/completions' )
def GetCompletions():
  request_data = RequestWrap( request.json )
  do_filetype_completion = _server_state.ShouldUseFiletypeCompleter(
    request_data )
  LOGGER.debug( 'Using filetype completion: %s', do_filetype_completion )

  errors = None
  completions = None

  if do_filetype_completion:
    try:
      filetype_completer = _server_state.GetFiletypeCompleter(
        request_data[ 'filetypes' ] ) 
      completions = filetype_completer.ComputeCandidates( request_data )
    except Exception as exception:
      if request_data[ 'force_semantic' ]:
        # user explicitly asked for semantic completion, so just pass the error
        # back
        raise

      # store the error to be returned with results from the identifier
      # completer
      LOGGER.exception( 'Exception from semantic completer (using general)' )
      stack = traceback.format_exc()
      errors = [ BuildExceptionResponse( exception, stack ) ]

  if not completions and not request_data[ 'force_semantic' ]:
    completions = _server_state.GetGeneralCompleter().ComputeCandidates(
      request_data )

  return _JsonResponse(
      BuildCompletionResponse( completions if completions else [],
                               request_data[ 'start_column' ],
                               errors = errors ) )

在ShouldUseFiletypeCompleter函数中,会判断协议中是否设置了force_semantic选项,

#@file: YouCompleteMe/third_party/ycmd/ycmd/server_state.py
  def ShouldUseFiletypeCompleter( self, request_data ):
    """Determines whether or not the semantic completion should be called for
    completion request."""
    filetypes = request_data[ 'filetypes' ]
    if not self.FiletypeCompletionUsable( filetypes ):
      # don't use semantic, ignore whether or not the user requested forced
      # completion as that's not relevant to signatures.
      return False 

    if request_data[ 'force_semantic' ]:
      # use semantic, and it was forced
      return True

    filetype_completer = self.GetFiletypeCompleter( filetypes )
    # was not forced. check the conditions for triggering
    return filetype_completer.ShouldUseNow( request_data )
  def ShouldUseNowInner( self, request_data ):
    if not self.completion_triggers:
      return False

    current_line = request_data[ 'line_value' ]
    start_codepoint = request_data[ 'start_codepoint' ] - 1 
    column_codepoint = request_data[ 'column_codepoint' ] - 1 
    filetype = self._CurrentFiletype( request_data[ 'filetypes' ] ) 

    return self.completion_triggers.MatchesForFiletype( current_line,
                                                        start_codepoint,
                                                        column_codepoint,
                                                        filetype )

如果没有设置force_semantic,则判断输入内容是否触发了语言定制的触发词(trigger)。

默认的文件类型配置中,C++使用的是常见的" '->', '.', '::' "。

#@file: YouCompleteMe/third_party/ycmd/ycmd/completers/completer_utils.py
DEFAULT_FILETYPE_TRIGGERS = {
  'c' : [ '->', '.' ],
  'objc,objcpp' : [
    '->',
    '.',
    r're!\[[_a-zA-Z]+\w*\s',    # bracketed calls
    r're!^\s*[^\W\d]\w*\s',     # bracketless calls
    r're!\[.*\]\s',             # method composition
  ],
  'ocaml' : [ '.', '#' ],
  'cpp,cuda,objcpp,cs' : [ '->', '.', '::' ],
  'perl' : [ '->' ],
  'php' : [ '->', '::' ],
  ( 'd,'
    'elixir,'
    'go,'
    'gdscript,'
    'groovy,'
    'java,'
    'javascript,'
    'javascriptreact,'
    'julia,'
    'perl6,'
    'python,'
    'scala,'
    'typescript,'
    'typescriptreact,'
    'vb' ) : [ '.' ],
  'ruby,rust' : [ '.', '::' ],
  'lua' : [ '.', ':' ],
  'erlang' : [ ':' ],
}   

parser

YCM还提供了提示文件语法错误的功能,这个功能明显和complete实现机制不同:这种全局文件提示需要对整个文件进行编译,然后解析编译器输出的语法错误。

和complete类似,ycm是通过OnFileReadyToParse>>self.CurrentBuffer().SendParseRequest( extra_data )>>EventNotification( 'FileReadyToParse'extra_data = extra_data )。此时ycmd会触发后端对整个buffer的语法编译。

理所当然的,这种全量编译的触发时机和complete不同,当前vim会在下面场景中会触发文件重新parse命令:

离开insert,打开文件并识别出文件类型后,在normal模式下修改文件内容后。

这里有一个细节:在持续编辑的时候并不会触发重新parse。这也很合理,因为编辑没有完成,此时解析几乎一定会触发(无意义的)语法错误。

"YouCompleteMe/autoload/youcompleteme.vim
function! s:OnInsertLeave()
call s:OnFileReadyToParse()
function! s:OnTextChangedNormalMode()
call s:OnFileReadyToParse()
function! s:OnFileTypeSet()

手动语义complete

回到开始的问题:complete请求中可以提供"force_semantic"参数要求ycmd完成语义匹配,而YCM也提供了简单的配置项来主动触发这种语义提示:

function! s:SetUpKeyMappings()

  if !empty( g:ycm_key_invoke_completion )
    let invoke_key = g:ycm_key_invoke_completion

    " Inside the console, <C-Space> is passed as <Nul> to Vim
    if invoke_key ==# '<C-Space>'
      imap <Nul> <C-Space>
    endif

    silent! exe 'inoremap <unique> <silent> ' . invoke_key .
          \ ' <C-R>=<SID>RequestSemanticCompletion()<CR>'
  endif

注意其中关键的 let s:force_semantic = 1

function! s:RequestSemanticCompletion() abort
  if !s:AllowedToCompleteInCurrentBuffer()
    return ''
  endif 

  if get( b:, 'ycm_completing' )
    let s:force_semantic = 1
    let s:current_cursor_position = getpos( '.' )
    call s:StopPoller( s:pollers.completion )
    py3 ycm_state.SendCompletionRequest( True )

那么具体使用哪个快捷键呢?逐个查询vim insert模式下的ctrl快捷键,可以发现insert模式下ctrl-b的功能通常并不会用到

所以在vim的配置中增加下面配置,即可愉快的手动触发在insert模式下(使用 ctrl-b)语义匹配了:

let g:ycm_key_invoke_completion = '<c-b>'

最少触发字符

即使通用的identifier_completer,是否触发提示也有配置:ycm_min_num_of_chars_for_completion ,只是这个配置的生效不是vim客户端判断而是ycmd判断的。

# YouCompleteMe/third_party/ycmd/ycmd/completers/all/identifier_completer.py
  def ShouldUseNow( self, request_data ):
    return self.QueryLengthAboveMinThreshold( request_data )
# YouCompleteMe/third_party/ycmd/ycmd/completers/completer.py 
  def QueryLengthAboveMinThreshold( self, request_data ):
    # Note: calculation in 'characters' not bytes.
    query_length = ( request_data[ 'column_codepoint' ] -
                     request_data[ 'start_codepoint' ] )

    return query_length >= self.min_num_chars

outro

killer级别的应用,绝大部分人的需求/痛点都已经有解决方案,只是你是否知道,是否理解原理("生活中从不缺少美,而是缺少发现美的眼睛")。困难的反而是提出一个更好的需求,更别说一个好的解决方案了。

标签:completion,semantic,YouCompleteMe,data,self,parser,request,completer
From: https://www.cnblogs.com/tsecer/p/18474999

相关文章

  • SQLiteHeaderParser
    packagecom.tencent.map.dataengine.converter;importjava.io.FileInputStream;importjava.io.IOException;importjava.nio.ByteBuffer;importjava.nio.ByteOrder;publicclassSQLiteHeaderParser{publicstaticvoidmain(String[]args){Strin......
  • LogParser-LLM: Advancing Efficient Log Parsing with Large Language Models
    本文是LLM系列文章,针对《LogParser-LLM:AdvancingEfficientLogParsingwithLargeLanguageModels》的翻译。LogParser-LLM:利用大型语言模型推进高效日志解析摘要1引言2相关工作和动机3日志解析粒度4方法5实验6结论摘要日志是无处不在的数字足迹......
  • JsonParser.Feature各枚举项的作用
    枚举项作用ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER允许反斜杠转义任何字符。ALLOW_COMMENTS允许在JSON内容中包含注释。ALLOW_MISSING_VALUES允许在JSON数组中缺少值。ALLOW_NON_NUMERIC_NUMBERS允许非数字的数值(如NaN、Infinity)。ALLOW_NUMERIC_LEADING_Z......
  • Python的configparser模块中,ConfigParser和RawConfigParser的区别
    在Python的configparser模块中,ConfigParser()和RawConfigParser()是两个不同的类,用于解析配置文件。它们之间的主要区别在于对配置文件中的值进行处理的方式。一、区别1、ConfigParser()类是configparser模块的旧版本,它提供了一些额外的特性,如对配置文件中的值进行插值替换。......
  • CMake构建学习笔记17-uriparser库的构建和使用
    在连续论述了几篇关于CMake如何使用的文章之后,笔者也是感觉被掏空了。接下来几篇就还是回到构建依赖库的问题上,容笔者花时间找到更好的主题来介绍更多关于CMake使用干货。如何有的读者自信已经很熟悉这方面的知识,可以进行跳过,在需要的时候再进行查阅。uriparser是一个严格遵循RFC......
  • 【漏洞复现】NUUO网络视频录像机 css_parser.php 任意文件读取
            NUUO网络视频录像机(NetworkVideoRecorder,简称NVR)是NUUOInc.生产的一种专业视频监控设备,它广泛应用于零售、交通、教育、政府和银行等多个领域。能够同时管理多个IP摄像头,实现视频录制、存储、回放及远程监控等功能。它采用先进的视频处理技术,提供高清、流畅......
  • JsonConfigurationFileParser
    internalclassProgram{staticasyncTaskMain(string[]args){varroot=newRoot{Demo1=newDemo1{Name="Demo1",Data=newDemo2{Name="Demo2"......
  • Apache顶级项目ShardingSphere — SQL Parser的设计与实现
    导语:SQL作为现代计算机行业的数据处理事实标准,是目前最重要的数据处理接口之一,从传统的DBMS(如MySQL、Oracle),到主流的计算框架(如spark,flink)都提供了SQL的解析引擎,因此想对sql进行精细化的操作,一定离不开SQLParser。ApacheShardingSphere是一套开源的分布式数据库中间件解决方......
  • Apache顶级项目ShardingSphere — SQL Parser的设计与实现
    导语:SQL作为现代计算机行业的数据处理事实标准,是目前最重要的数据处理接口之一,从传统的DBMS(如MySQL、Oracle),到主流的计算框架(如spark,flink)都提供了SQL的解析引擎,因此想对sql进行精细化的操作,一定离不开SQLParser。ApacheShardingSphere是一套开源的分布式数据库中间件解决方案......
  • 构建高效NLP管道:PydanticOutputParser与Langchain的结合
    PydanticOutputParser是一个用于解析语言模型输出的实用工具,它允许用户指定一个Pydantic模型,并查询语言模型以生成符合该模型的JSON输出。这个工具特别有用,因为它可以帮助开发者确保从语言模型获得的结构化数据符合预期的格式,从而简化了数据处理和集成的过程。使用Pyda......