Erlang Plugin for NetBeans in Scala#10: Code Completion
Caoyuan Blog - - February 27, 2009Implementing Code-Completion is a bit complex, but you can got it work gradually. At the first step, you can implement Code-Completion for local vars/functions only, then, with the indexed supporting, you can add completion for remote functions.
You should define some kinds of completion proposal, which may show different behaviors when they are popped up and guard you followed steps. For example, a function proposal can auto-fill parameters, on the other side, a keyword proposal just complete itself.
The completion proposal classes are defined in ErlangComplectionProposal.scala, which implemented CSL’s interface CompletionProposal. you may notice that the function proposal is the most complex one, which should handle parameters information.
Then, you should implement CSL’s interface CodeCompletionHandler, for Erlang, it’s ErlangCodeCompletion, where, the key method is:
override def complete(context:CodeCompletionContext) :CodeCompletionResult = { this.caseSensitive = context.isCaseSensitive val pResult = context.getParserResult.asInstanceOf[ErlangParserResult] val lexOffset = context.getCaretOffset val prefix = context.getPrefix match { case null => "" case x => x } val kind = if (context.isPrefixMatch) QuerySupport.Kind.PREFIX else QuerySupport.Kind.EXACT val queryType = context.getQueryType val doc = LexUtil.document(pResult, true) match { case None => return CodeCompletionResult.NONE case Some(x) => x.asInstanceOf[BaseDocument] } val proposals = new ArrayList[CompletionProposal] val completionResult = new DefaultCompletionResult(proposals, false) // Read-lock due to Token hierarchy use doc.readLock try { val astOffset = LexUtil.astOffset(pResult, lexOffset) if (astOffset == -1) { return CodeCompletionResult.NONE } val root = pResult.rootScope match { case None => return CodeCompletionResult.NONE case Some(x) => x } val th = LexUtil.tokenHierarchy(pResult).get val fileObject = LexUtil.fileObject(pResult).get val request = new CompletionRequest request.completionResult = completionResult request.result = pResult request.lexOffset = lexOffset request.astOffset = astOffset request.index = ErlangIndex.get(pResult) request.doc = doc request.info = pResult request.prefix = prefix request.th = th request.kind = kind request.queryType = queryType request.fileObject = fileObject request.anchor = lexOffset - prefix.length request.root = root ErlangCodeCompletion.request = request val token = LexUtil.token(doc, lexOffset - 1) match { case None => return completionResult case Some(x) => x } token.id match { case ErlangTokenId.LineComment => // TODO - Complete symbols in comments? return completionResult case ErlangTokenId.StringLiteral => //completeStrings(proposals, request) return completionResult case _ => } val ts = LexUtil.tokenSequence(th, lexOffset - 1) match { case None => return completionResult case Some(x) => x.move(lexOffset - 1) if (!x.moveNext && !x.movePrevious) { return completionResult } x } val closetToken = LexUtil.findPreviousNonWsNonComment(ts) if (root != null) { val sanitizedRange = pResult.sanitizedRange val offset = if (sanitizedRange != OffsetRange.NONE && sanitizedRange.containsInclusive(astOffset)) { sanitizedRange.getStart } else astOffset val call = Call(null, null, false) findCall(root, ts, th, call, 0) val prefixBak = request.prefix call match { case Call(null, _, _) => case Call(base, _, false) => // it's not a call, but may be candicate for module name, try to get modules and go-on completeModules(base, proposals, request) case Call(base, select, true) => if (select != null) { request.prefix = call.select.text.toString } else { request.prefix = "" } completeModuleFunctions(call.base, proposals, request) // Since is after a ":", we won't added other proposals, just return now whatever return completionResult } request.prefix = prefixBak completeLocals(proposals, request) } completeKeywords(proposals, request) } finally { doc.readUnlock } completionResult }
For a Erlang function call, you should check the tokens surrounding the caret to get the call’s base name and select first, which is done by a method findCall:
private def findCall(rootScope:AstRootScope, ts:TokenSequence[TokenId], th:TokenHierarchy[_], call:Call, times:Int) :Unit = { assert(rootScope != null) val closest = LexUtil.findPreviousNonWsNonComment(ts) val idToken = closest.id match { case ErlangTokenId.Colon => call.caretAfterColon = true // skip RParen if it's the previous if (ts.movePrevious) { val prev = LexUtil.findPreviousNonWs(ts) if (prev != null) { prev.id match { case ErlangTokenId.RParen => LexUtil.skipPair(ts, ErlangTokenId.LParen, ErlangTokenId.RParen, true) case ErlangTokenId.RBrace => LexUtil.skipPair(ts, ErlangTokenId.LBrace, ErlangTokenId.RBrace, true) case ErlangTokenId.RBracket => LexUtil.skipPair(ts, ErlangTokenId.LBracket, ErlangTokenId.RBracket, true) case _ => } } } LexUtil.findPrevIncluding(ts, LexUtil.CALL_IDs) case id if LexUtil.CALL_IDs.contains(id) => closest case _ => null } if (idToken != null) { times match { case 0 if call.caretAfterColon => call.base = idToken case 0 if ts.movePrevious => LexUtil.findPreviousNonWsNonComment(ts) match { case null => call.base = idToken case prev if prev.id == ErlangTokenId.Colon => call.caretAfterColon = true call.select = idToken findCall(rootScope, ts, th, call, times + 1) case _ => call.base = idToken } case _ => call.base = idToken } } } case class Call(var base:Token[TokenId], var select:Token[TokenId], var caretAfterColon:Boolean)
To complete a remote function call, you may need to visit outer modules, which needs an indexer, so as the first step, you can just ignore it, go straight to complete local vars/functions, or keywords:
private def completeLocals(proposals:List[CompletionProposal], request:CompletionRequest) :Unit = { val prefix = request.prefix val kind = request.kind val pResult = request.result val root = request.root val closestScope = root.closestScope(request.th, request.astOffset) match { case None => return case Some(x) => x } val localVars = closestScope.visibleDfns(ElementKind.VARIABLE) localVars ++= closestScope.visibleDfns(ElementKind.PARAMETER) localVars.filter{v => filterKind(kind, prefix, v.name)}.foreach{v => proposals.add(new PlainProposal(v, request.anchor)) } val localFuns = closestScope.visibleDfns(ElementKind.METHOD) localFuns.filter{f => filterKind(kind, prefix, f.name)}.foreach{f => proposals.add(new FunctionProposal(f, request.anchor)) } } private def completeKeywords(proposals:List[CompletionProposal], request:CompletionRequest) :Unit = { val prefix = request.prefix val itr = LexerErlang.ERLANG_KEYWORDS.iterator while (itr.hasNext) { val keyword = itr.next if (startsWith(keyword, prefix)) { proposals.add(new KeywordProposal(keyword, null, request.anchor)) } } }
There is a function “def visibleDfns(kind:ElementKind) :ArrayBuffer[AstDfn]” in AstRootScope.scala, if you’ve put definition items properly in scopes, it should handle the visibility automatically.
Now, register it in ErlangLanguage.scala:
override def getCompletionHandler = new ErlangCodeCompletion
As usual, run it, you got:
The local functions and vars are proposed plus the keywords. BTW, I’ve fixed this feature for Scala plug-in.
Categories: Blogs Caoyuan Blog
Comments
No comments so far, you could be the first.Add comment
Erlang on Twitter
» ITJobs_EU_UK (ITJobs_EU_UK): #JB Ruby Developer ( Ruby / RoR Erlang LAMP ): Job Description : Ruby Developer / Software Engineer Location: Lo… http://t.co/74omWQ9m
» udzura (Uchio KONDO): 文字列操作が弱い、は今のErlangではfalseであると
» udzura (Uchio KONDO): Erlang , R14 あたりからutf-8の文字列の扱いに強くなったとのこと #shinjukuex
» winda_lestari63 (winda cliquers): gg usja di pikirin prins ank itw gjhe..RT:@Prinsia_2140
@Erlang_ABNIC @ji_bero @erlang_abnic @rb_120511
» ErlangSolutions (Erlang Solutions): Want to join the best of the best of the best? ESL is hiring 40 engineers! Join our Linkedin Group to keep updated http://t.co/CDB7qYeI
» Prinsia_2140 (˘⌣˘ pRiNsiAH ˘⌣˘): @Erlang_ABNI : http://t.co/j61ODMNv
» Erlang_ABNIC (Erlangga .A): JERA :) RT @AgnesMonicaFanz: #acakhuruf A-R-J-E . klue : kapok,lagu. jawab yuk NIC !
» Fedora_Updates (====================): Fedora 17 Update: erlang-misultin-0.9-1.fc17 http://t.co/u3ABUDkz
» HornedKavu (Max Riveiro): RT @shwars: Вчера в докладе career.ru на #itedu были озвучены новые востреб.профессии на IT-рынке: Haskell-, Lisp- и Erlang-программист!
» phyrexianengine (Vasily K.): RT @shwars: Вчера в докладе career.ru на #itedu были озвучены новые востреб.профессии на IT-рынке: Haskell-, Lisp- и Erlang-программист!
Statistics
Number of aggregated posts: 10498
Number of comments: 2115
Most recent article: May 15, 2012
Latest comments
» cheap soccer jerseys on Memory Models in Erlang vs Java: Nice discussion here,you are doing a great job. i was looking for this information. i found it on your page…
» mandesejohn on Couchbase Meetup at new HQ: Thanks for sharing experience. It should be really a great post. It should be knowledgeable and informative. Keep it up. flower delivery columbus ohio
» vermaseo on Scale means Skills: I’m surprised people are still commenting about this. George has been moved on to bigger and better things with the president for awhile now.ledikanten
