/******************************************************************************* * Copyright (c) 2016 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package com.googlecode.goclipse.tooling.gocode; import java.util.EnumSet; import java.util.List; import java.util.regex.Pattern; import melnorme.lang.tooling.CompletionProposalKind; import melnorme.lang.tooling.EAttributeFlag; import melnorme.lang.tooling.EProtection; import melnorme.lang.tooling.ElementAttributes; import melnorme.lang.tooling.ToolCompletionProposal; import melnorme.lang.tooling.ast.SourceRange; import melnorme.lang.tooling.toolchain.ops.AbstractToolResultParser; import melnorme.lang.tooling.toolchain.ops.OperationSoftFailure; import melnorme.lang.utils.parse.StringCharSource; import melnorme.utilbox.collections.ArrayList2; import melnorme.utilbox.collections.Indexable; import melnorme.utilbox.misc.Pair; import melnorme.utilbox.misc.StringUtil; public abstract class GocodeOutputParser2 extends AbstractToolResultParser<ArrayList2<ToolCompletionProposal>> { protected final int offset; protected final String source; protected final String completionPrefix; public GocodeOutputParser2(int offset, String source) { this.offset = offset; this.source = source; completionPrefix = getWordBeforeOffset(source, offset); } public static String getWordBeforeOffset(String source, int offset) { int ix = offset; while(ix > 0 && Character.isJavaIdentifierPart(source.charAt(ix-1))){ ix--; } return source.substring(ix, offset); } @Override protected String getToolName() { return "gocode"; } @Override public ArrayList2<ToolCompletionProposal> parseOutput(StringCharSource parseSource) throws OperationSoftFailure { String stdout = parseSource.getSource(); if (stdout.startsWith("PANIC")) { handleParseError(new OperationSoftFailure("PANIC from gocode - likely go/gocode version mismatch?")); } List<String> completions = ArrayList2.create(GocodeCompletionOperation.LINE_SPLITTER.split(stdout)); ArrayList2<ToolCompletionProposal> baseResults = new ArrayList2<>(); for (String completionEntry : completions) { ToolCompletionProposal proposal = parseCompletion(completionEntry); if(proposal != null) { baseResults.add(proposal); } } return baseResults; } protected void handleParseError(OperationSoftFailure ce) throws OperationSoftFailure { throw ce; } protected abstract void logWarning(String message); protected ToolCompletionProposal parseCompletion(final String completionEntry) { if(completionEntry.trim().isEmpty()) { return null; } String line = completionEntry; String kindString = StringUtil.segmentUntilMatch(line, ",,"); if(kindString == null) { logWarning("Invalid gocode completion result line: " + line); return null; } line = StringUtil.segmentAfterMatch(line, ",,"); String identifier = StringUtil.segmentUntilMatch(line, ",,"); if(identifier == null) { logWarning("Invalid gocode completion result line: " + completionEntry); return null; } String spec = StringUtil.segmentAfterMatch(line, ",,"); String label = identifier; String typeLabel = null; EnumSet<EAttributeFlag> flagsSet = EnumSet.noneOf(EAttributeFlag.class); CompletionProposalKind kind = CompletionProposalKind.UNKNOWN; String fullReplaceString = identifier; Indexable<SourceRange> subRanges = null; if (kindString.equals("type")) { if (spec.equals("interface")) { kind = CompletionProposalKind.INTERFACE; } else if (spec.equals("struct")) { kind = CompletionProposalKind.STRUCT; } else if (spec.equals("built-in")) { kind = CompletionProposalKind.NATIVE; } else { kind = CompletionProposalKind.TYPE_DECL; typeLabel = ": " + spec; } } else if (kindString.equals("package")) { kind = CompletionProposalKind.PACKAGE; } else if (kindString.equals("func")) { kind = CompletionProposalKind.FUNCTION; spec = spec.replaceFirst("^func", ""); typeLabel = StringUtil.segmentAfterMatch(spec, ")"); String fnParams = ""; if(typeLabel != null) { fnParams = StringUtil.trimEnd(spec, typeLabel); typeLabel = typeLabel.trim(); Pair<String, ArrayList2<SourceRange>> pair = getParamsString(identifier.length(), fnParams); String paramsString = pair.getFirst(); subRanges = pair.getSecond(); fullReplaceString = identifier + paramsString; } label = label + fnParams; } else if (kindString.equals("var") || kindString.equals("const")) { kind = CompletionProposalKind.VARIABLE; if(!spec.trim().isEmpty()) { typeLabel = ": " + spec; } if (kindString.equals("const")) { flagsSet.add(EAttributeFlag.CONST); } } else { logWarning("Unknown element kind: " + kindString); kind = CompletionProposalKind.UNKNOWN; } EProtection prot = null; if(kind != CompletionProposalKind.PACKAGE && kind != CompletionProposalKind.NATIVE) { if (identifier.length() > 0 && Character.isLowerCase(identifier.charAt(0))) { prot = EProtection.PRIVATE; } } ElementAttributes attributes = new ElementAttributes(prot, flagsSet); return new ToolCompletionProposal( offset - completionPrefix.length(), completionPrefix.length(), identifier, label, kind, attributes, typeLabel, null, null, fullReplaceString, subRanges); } protected Pair<String, ArrayList2<SourceRange>> getParamsString(int baseOffset, String fnParams) { fnParams = fnParams.trim(); if(!fnParams.startsWith("(") && fnParams.endsWith(")")) { return Pair.create("", null); } String parse = fnParams.substring(1, fnParams.length()-1); if(parse.trim().isEmpty()) { return Pair.create("()", null); } String[] params = parse.split(Pattern.quote(",")); ArrayList2<SourceRange> subRanges = new ArrayList2<>(); StringBuilder paramsString = new StringBuilder("("); baseOffset += 1; for(int i = 0; i < params.length; i++) { if(i != 0) { paramsString.append(", "); baseOffset += 2; } String paramName = StringUtil.substringUntilMatch(params[i].trim(), " "); paramsString.append(paramName); subRanges.add(new SourceRange(baseOffset, paramName.length())); baseOffset += paramName.length(); } paramsString.append(")"); return Pair.create(paramsString.toString(), subRanges); } }