mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-24 02:19:15 +00:00
initial working language server
This commit is contained in:
53
extension.js
53
extension.js
@@ -1,11 +1,62 @@
|
||||
// The module 'vscode' contains the VS Code extensibility API
|
||||
// Import the module and reference it with the alias vscode in your code below
|
||||
const path = require('path');
|
||||
const vscode = require('vscode');
|
||||
const { LanguageClient, TransportKind, } = require('vscode-languageclient');
|
||||
const { AndroidContentProvider } = require('./src/contentprovider');
|
||||
const { openLogcatWindow } = require('./src/logcat');
|
||||
const { selectAndroidProcessID } = require('./src/process-attach');
|
||||
const { selectTargetDevice } = require('./src/utils/device');
|
||||
|
||||
/** @type {LanguageClient} */
|
||||
let client;
|
||||
|
||||
function activateLanguageClient(context) {
|
||||
// The server is implemented in node
|
||||
let serverModule = context.asAbsolutePath(path.join('langserver', 'server.js'));
|
||||
// The debug options for the server
|
||||
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
|
||||
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
|
||||
|
||||
// If the extension is launched in debug mode then the debug server options are used
|
||||
// Otherwise the run options are used
|
||||
/** @type {import('vscode-languageclient').ServerOptions} */
|
||||
let serverOptions = {
|
||||
run: {
|
||||
module: serverModule,
|
||||
transport: TransportKind.ipc,
|
||||
},
|
||||
debug: {
|
||||
module: serverModule,
|
||||
transport: TransportKind.ipc,
|
||||
options: debugOptions
|
||||
}
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
/** @type {import('vscode-languageclient').LanguageClientOptions} */
|
||||
let clientOptions = {
|
||||
// Register the server for plain text documents
|
||||
documentSelector: [{ scheme: 'file', language: 'java' }],
|
||||
synchronize: {
|
||||
// Notify the server about file changes to '.java files contained in the workspace
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher('**/.java')
|
||||
}
|
||||
};
|
||||
|
||||
// Create the language client and start the client.
|
||||
client = new LanguageClient(
|
||||
'androidJavaLanguageServer',
|
||||
'Java (Android)',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
|
||||
// Start the client. This will also launch the server
|
||||
return client.start();
|
||||
}
|
||||
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
function activate(context) {
|
||||
@@ -50,6 +101,8 @@ function activate(context) {
|
||||
// the debugger requires a string value to be returned
|
||||
return JSON.stringify(o);
|
||||
}),
|
||||
|
||||
activateLanguageClient(context),
|
||||
];
|
||||
|
||||
context.subscriptions.splice(context.subscriptions.length, 0, ...disposables);
|
||||
|
||||
646
langserver/java/mti.js
Normal file
646
langserver/java/mti.js
Normal file
@@ -0,0 +1,646 @@
|
||||
|
||||
/**
|
||||
* @param {number} ref
|
||||
* @param {MTI} mti
|
||||
* @returns {string}
|
||||
*/
|
||||
function packageNameFromRef(ref, mti) {
|
||||
if (typeof ref !== 'number') {
|
||||
return null;
|
||||
}
|
||||
if (ref < 16) {
|
||||
return KnownPackages[ref];
|
||||
}
|
||||
return mti.minified.rp[ref - 16];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} ref
|
||||
* @param {MTI} mti
|
||||
*/
|
||||
function typeFromRef(ref, mti) {
|
||||
if (typeof ref !== 'number') {
|
||||
return null;
|
||||
}
|
||||
if (ref < 16) {
|
||||
return KnownTypes[ref];
|
||||
}
|
||||
return mti.referenced.types[ref - 16];
|
||||
}
|
||||
|
||||
function indent(s) {
|
||||
return '\n' + s.split('\n').map(s => ` ${s}`).join('\n');
|
||||
}
|
||||
|
||||
class MinifiableInfo {
|
||||
|
||||
constructor(minified) {
|
||||
this.minified = minified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a commented form of docs with a newline at the end.
|
||||
*/
|
||||
fmtdocs() {
|
||||
// the docs field is always d in the minified objects
|
||||
const d = this.minified.d;
|
||||
return d ? `/**\n * ${d.replace(/\n/g,'\n *')}\n */\n` : '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minified Type Information
|
||||
*
|
||||
* Each MTI instance represents a Java unit (a single source file or a compiled class file).
|
||||
* The mti JSON format is minimalistic to keep the size small - the Android framework has over 8000 classes in
|
||||
* it, so keeping the information as small as possible is beneficial.
|
||||
* ```
|
||||
mti: {
|
||||
rp:[], // referenced packages
|
||||
rt:[], // referenced types
|
||||
it:[{ // implemented types
|
||||
m:0, // type modifiers
|
||||
n:'', // type name (in X$Y format)
|
||||
p:null, // owner package
|
||||
v:[], // type vars
|
||||
e:null, // extends 0(class)/[0,...](interface)/null(unknown)
|
||||
i:[], // implements [0,...]
|
||||
c:[], // constructors [{m:0,p:[]},...]
|
||||
f:[], // fields {m:0,n:'',t:0},
|
||||
g:[], // methods {n:'',s:[{m:0,t:0,p:[{m:0,t:0,n:''},...]}]}
|
||||
u:[], // subtypes [0,...]
|
||||
d:'', // type docs
|
||||
}]
|
||||
},
|
||||
```
|
||||
*/
|
||||
class MTI extends MinifiableInfo {
|
||||
|
||||
/**
|
||||
* @param {{rp:[], rt:[], it:[]}} mti
|
||||
*/
|
||||
constructor(mti) {
|
||||
super(mti);
|
||||
// initialise the lists of referenced packages and types
|
||||
this.referenced = {
|
||||
/** @type {string[]} */
|
||||
packages: mti.rp,
|
||||
|
||||
/** @type {ReferencedType[]} */
|
||||
types: [],
|
||||
}
|
||||
// because ReferencedType can make use of earlier reference types, we must add them sequentially
|
||||
// instead of using mti.rt.map()
|
||||
for (let t of mti.rt) {
|
||||
this.referenced.types.push(new ReferencedType(this, t))
|
||||
}
|
||||
|
||||
// add the types implemented by this unit
|
||||
this.types = mti.it.map(it => new MTIType(this, it));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack all the classes from the given JSON
|
||||
* @param {string} filename
|
||||
*/
|
||||
static unpackJSON(filename) {
|
||||
const o = JSON.parse(require('fs').readFileSync(filename, 'utf8'));
|
||||
delete o.NOTICES;
|
||||
const types = [];
|
||||
for (let pkg in o) {
|
||||
for (let cls in o[pkg]) {
|
||||
const unit = new MTI(o[pkg][cls]);
|
||||
types.push(...unit.types);
|
||||
}
|
||||
}
|
||||
return {
|
||||
packages: Object.keys(o).sort(),
|
||||
types: types.sort((a,b) => a.minified.n.localeCompare(b.minified.n)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A ReferencedType encodes a type used by a class, interface or enum.
|
||||
* ```
|
||||
* {
|
||||
* n: string | typeref - name or base typeref (for arrays and generic types)
|
||||
* p?: pkgref - package the type is declared in (undefined for primitives)
|
||||
* g?: typeref[] - generic type parameters
|
||||
* a?: number - array dimensions
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* A typeref value < 16 is a lookup into the KnownTypes array.
|
||||
*
|
||||
* All other types have a typeref >= 16 and an associated package reference.
|
||||
*
|
||||
* The packageref is a lookup into the MTIs pt array which lists package names.
|
||||
*/
|
||||
class ReferencedType extends MinifiableInfo {
|
||||
|
||||
/**
|
||||
* @param {MTI} unit
|
||||
* @param {*} mti
|
||||
* @param {string|false} [pkg_or_prim] predefined package name, an empty string for default packages or false for primitives
|
||||
* @param {*} [default_value]
|
||||
*/
|
||||
constructor(unit, mti, pkg_or_prim, default_value = null) {
|
||||
super(mti);
|
||||
let baseType;
|
||||
if (typeof mti.n === 'number') {
|
||||
baseType = typeFromRef(mti.n, unit);
|
||||
}
|
||||
this.parsed = {
|
||||
package: pkg_or_prim
|
||||
|| ((pkg_or_prim === false)
|
||||
? undefined
|
||||
: packageNameFromRef(mti.p, unit)
|
||||
),
|
||||
|
||||
/** @type {ReferencedType} */
|
||||
baseType,
|
||||
|
||||
/** @type {ReferencedType[]} */
|
||||
typeParams: mti.g && mti.g.map(t => typeFromRef(t, unit)),
|
||||
|
||||
/** @type {string} */
|
||||
arr: '[]'.repeat(mti.a | 0),
|
||||
}
|
||||
this.defaultValue = default_value;
|
||||
}
|
||||
|
||||
get isPrimitive() { return this.parsed.package === undefined }
|
||||
|
||||
get package() { return this.parsed.package }
|
||||
|
||||
get name() {
|
||||
// note: names in enclosed types are in x$y format
|
||||
const n = this.parsed.baseType ? this.parsed.baseType.name : this.minified.n;
|
||||
const type_params = this.parsed.typeParams
|
||||
? `<${this.parsed.typeParams.map(tp => tp.name).join(',')}>`
|
||||
: ''
|
||||
return `${n}${type_params}${this.parsed.arr}`;
|
||||
}
|
||||
|
||||
get dottedName() {
|
||||
return this.name.replace(/[$]/g, '.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MTIType encodes a complete type (class, interface or enum)
|
||||
* ```
|
||||
* {
|
||||
* d: string - type docs
|
||||
* p: pkgref - the package this type belongs to
|
||||
* n: string - type name (in x$y format for enclosed types)
|
||||
* v: typeref[] - generic type variables
|
||||
* e: typeref | typeref[] - super/extends type (single value for classes, array for interfaces)
|
||||
* i: typeref[] - interface types
|
||||
* f: mtifield[] - fields
|
||||
* c: mtictrs[] - constructors
|
||||
* g: mtimethod[] - methods
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class MTIType extends MinifiableInfo {
|
||||
|
||||
/**
|
||||
* @param {MTI} unit
|
||||
* @param {*} mti
|
||||
*/
|
||||
constructor(unit, mti) {
|
||||
super(mti);
|
||||
this.parsed = {
|
||||
package: packageNameFromRef(mti.p, unit),
|
||||
|
||||
/** @type {ReferencedType[]} */
|
||||
typevars: mti.v.map(v => typeFromRef(v, unit)),
|
||||
|
||||
/** @type {ReferencedType|ReferencedType[]} */
|
||||
extends: Array.isArray(mti.e)
|
||||
? mti.e.map(e => typeFromRef(e, unit))
|
||||
: typeFromRef(mti.e, unit),
|
||||
|
||||
/** @type {ReferencedType[]} */
|
||||
implements: mti.i.map(i => typeFromRef(i, unit)),
|
||||
|
||||
/** @type {MTIField[]} */
|
||||
fields: mti.f.map(f => new MTIField(unit, f)),
|
||||
|
||||
/** @type {MTIConstructor[]} */
|
||||
constructors: mti.c.map(c => new MTIConstructor(unit, c)),
|
||||
|
||||
/**
|
||||
* MTI method are grouped by name - we split them here
|
||||
* @type {MTIMethod[]}
|
||||
*/
|
||||
methods: mti.g.reduce((arr, m) => [...arr, ...MTIMethod.split(unit, this, m)], []),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* type docs
|
||||
* @type {string}
|
||||
*/
|
||||
get docs() { return this.minified.d }
|
||||
|
||||
/**
|
||||
* type modifiers
|
||||
* @type {number}
|
||||
*/
|
||||
get modifiers() { return this.minified.m }
|
||||
|
||||
/**
|
||||
* type name (in x$y format for enclosed types)
|
||||
* @type {string}
|
||||
*/
|
||||
get name() { return this.minified.n }
|
||||
|
||||
get dottedRawName() { return this.minified.n.replace(/[$]/g, '.') };
|
||||
|
||||
get dottedName() {
|
||||
const t = this.typevars.map(t => t.name).join(',');
|
||||
return t ? `${this.dottedRawName}<${t}>` : this.dottedRawName;
|
||||
};
|
||||
|
||||
/**
|
||||
* type name with no qualifiers
|
||||
* @type {string}
|
||||
*/
|
||||
get simpleRawName() { return this.minified.n.match(/[^$]+$/)[0] }
|
||||
|
||||
/**
|
||||
* package this type belongs to
|
||||
*/
|
||||
get package() { return this.parsed.package }
|
||||
|
||||
get typeKind() {
|
||||
const m = this.minified.m;
|
||||
return (m & TypeModifiers.enum)
|
||||
? 'enum' : (m & TypeModifiers.interface)
|
||||
? 'interface' : (m & TypeModifiers['@interface'])
|
||||
? '@interface' : 'class';
|
||||
}
|
||||
|
||||
/**
|
||||
* generic type variables
|
||||
*/
|
||||
get typevars() { return this.parsed.typevars }
|
||||
|
||||
/**
|
||||
* class or interface extends.
|
||||
* Note that classes have a single extend type, but interfaces have an array.
|
||||
*/
|
||||
get extends() { return this.parsed.extends }
|
||||
|
||||
/**
|
||||
* class implements
|
||||
*/
|
||||
get implements() { return this.parsed.implements }
|
||||
|
||||
/**
|
||||
* @type {MTIConstructor[]}
|
||||
*/
|
||||
get constructors() { return this.parsed.constructors }
|
||||
|
||||
/**
|
||||
* @type {MTIField[]}
|
||||
*/
|
||||
get fields() { return this.parsed.fields }
|
||||
|
||||
/**
|
||||
* @type {MTIMethod[]}
|
||||
*/
|
||||
get methods() { return this.parsed.methods }
|
||||
|
||||
toSource() {
|
||||
let constructors = [], typevars = '', ex = '', imp = '';
|
||||
|
||||
// only add constructors if there's more than just the default constructor
|
||||
if (!((this.constructors.length === 1) && (this.constructors[0].parameters.length === 0))) {
|
||||
constructors = this.constructors;
|
||||
}
|
||||
|
||||
if (this.typevars.length) {
|
||||
typevars = `<${this.typevars.map(tv => tv.name).join(',')}>`;
|
||||
}
|
||||
|
||||
if (this.extends) {
|
||||
// only add extends if it's not derived from java.lang.Object
|
||||
if (this.extends !== KnownTypes[3]) {
|
||||
const x = Array.isArray(this.extends) ? this.extends : [this.extends];
|
||||
ex = `extends ${x.map(type => type.dottedName).join(', ')} `;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.implements.length) {
|
||||
imp = `implements ${this.implements.map(type => type.dottedName).join(', ')} `;
|
||||
}
|
||||
|
||||
return [
|
||||
`${this.fmtdocs()}${typemods(this.modifiers)} ${this.simpleRawName}${typevars} ${ex}${imp}{`,
|
||||
...this.fields.map(f => indent(f.toSource())),
|
||||
...constructors.map(c => indent(c.toSource())),
|
||||
...this.methods.map(m => indent(m.toSource())),
|
||||
`}`
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MTIField encodes a single type field.
|
||||
* ```
|
||||
* {
|
||||
* d: string - docs
|
||||
* m: number - access modifiers
|
||||
* n: string - field name
|
||||
* t: typeref - field type
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class MTIField extends MinifiableInfo {
|
||||
|
||||
/**
|
||||
* @param {MTI} owner
|
||||
* @param {*} mti
|
||||
*/
|
||||
constructor(owner, mti) {
|
||||
super(mti);
|
||||
this.parsed = {
|
||||
type: typeFromRef(mti.t, owner),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get modifiers() { return this.minified.m }
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
get docs() { return this.minified.d }
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
get name() { return this.minified.n }
|
||||
|
||||
/**
|
||||
* @type {ReferencedType}
|
||||
*/
|
||||
get type() { return this.parsed.type }
|
||||
|
||||
toSource() {
|
||||
return `${this.fmtdocs()}${access(this.modifiers)}${this.type.dottedName} ${this.name} = ${this.type.defaultValue};`
|
||||
}
|
||||
}
|
||||
|
||||
class MTIMethodBase extends MinifiableInfo {}
|
||||
|
||||
/**
|
||||
* MTIContructor encodes a single type constructor.
|
||||
* ```
|
||||
* {
|
||||
* d: string - docs
|
||||
* m: number - access modifiers
|
||||
* p: mtiparam[] - constructor parameters
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class MTIConstructor extends MTIMethodBase {
|
||||
|
||||
/**
|
||||
* @param {MTI} owner
|
||||
* @param {*} mti
|
||||
*/
|
||||
constructor(owner, mti) {
|
||||
super(mti);
|
||||
this.parsed = {
|
||||
typename: owner.minified.it[0].n,
|
||||
/** @type {MTIParameter[]} */
|
||||
parameters: mti.p.map(p => new MTIParameter(owner, p)),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get modifiers() { return this.minified.m }
|
||||
|
||||
get docs() { return this.minified.d }
|
||||
|
||||
/**
|
||||
* @type {MTIParameter[]}
|
||||
*/
|
||||
get parameters() { return this.parsed.parameters }
|
||||
|
||||
toSource() {
|
||||
const typename = this.parsed.typename.split('$').pop();
|
||||
return `${this.fmtdocs()}${access(this.modifiers)}${typename}(${this.parameters.map(p => p.toSource()).join(', ')}) {}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MTIMethod encodes a single type method.
|
||||
*
|
||||
* In minified form, methods are encoded as overloads - each entry
|
||||
* has a single name with one or more method signatures.
|
||||
* ```
|
||||
* {
|
||||
* d: string - docs
|
||||
* n: string - method name
|
||||
* s: [{
|
||||
* m: number - access modifiers
|
||||
* t: typeref - return type
|
||||
* p: mtiparam[] - method parameters
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class MTIMethod extends MTIMethodBase {
|
||||
|
||||
/**
|
||||
* @param {MTI} unit
|
||||
* @param {MTIType} type
|
||||
* @param {string} name
|
||||
* @param {*} mti
|
||||
*/
|
||||
constructor(unit, type, name, mti) {
|
||||
super(mti);
|
||||
this.interfaceMethod = type.modifiers & 0x200;
|
||||
this.parsed = {
|
||||
name,
|
||||
/** @type {MTIParameter[]} */
|
||||
parameters: mti.p.map(p => new MTIParameter(unit, p)),
|
||||
/** @type {ReferencedType} */
|
||||
return_type: typeFromRef(mti.t, unit),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MTI} unit
|
||||
* @param {MTIType} type
|
||||
* @param {*} mti
|
||||
*/
|
||||
static split(unit, type, mti) {
|
||||
return mti.s.map(s => new MTIMethod(unit, type, mti.n, s));
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
get docs() { return this.minified.d }
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get modifiers() { return this.minified.m }
|
||||
|
||||
/**
|
||||
* @type {ReferencedType}
|
||||
*/
|
||||
get return_type() { return this.parsed.return_type }
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
get name() { return this.parsed.name }
|
||||
|
||||
/**
|
||||
* @type {MTIParameter[]}
|
||||
*/
|
||||
get parameters() { return this.parsed.parameters }
|
||||
|
||||
toSource() {
|
||||
let m = this.modifiers, body = ' {}';
|
||||
if (m & 0x400) {
|
||||
body = ';'; // abstract method - no body
|
||||
} else if (this.return_type.name !== 'void') {
|
||||
body = ` { return ${this.return_type.defaultValue}; }`;
|
||||
}
|
||||
if (this.interfaceMethod) {
|
||||
m &= ~0x400; // exclude abstract modifier as it's redundant
|
||||
}
|
||||
return `${this.fmtdocs()}${access(m)}${this.return_type.dottedName} ${this.name}(${this.parameters.map(p => p.toSource()).join(', ')})${body}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MTIParameter encodes a single method or constructor paramter
|
||||
* ```
|
||||
* {
|
||||
* m?: number - access modifiers (only 'final' is allowed)
|
||||
* t: typeref - parameter type
|
||||
* n: string - parameter name
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class MTIParameter extends MinifiableInfo {
|
||||
|
||||
/**
|
||||
* @param {MTI} owner
|
||||
* @param {*} mti
|
||||
*/
|
||||
constructor(owner, mti) {
|
||||
super(mti);
|
||||
this.parsed = {
|
||||
type: typeFromRef(mti.t, owner)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
get modifiers() { return this.minified.m | 0 }
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
get name() { return this.minified.n }
|
||||
|
||||
/**
|
||||
* @type {ReferencedType}
|
||||
*/
|
||||
get type() { return this.parsed.type }
|
||||
|
||||
toSource() {
|
||||
return `${access(this.modifiers)}${this.type.dottedName} ${this.name}`
|
||||
}
|
||||
}
|
||||
|
||||
const access_keywords = 'public private protected static final synchronized volatile transient native interface abstract strict'.split(' ');
|
||||
|
||||
/**
|
||||
* @param {number} modifier_bits
|
||||
*/
|
||||
function access(modifier_bits) {
|
||||
// convert the modifier bits into keywords
|
||||
const decls = access_keywords.filter((_,i) => modifier_bits & (1 << i));
|
||||
if (decls.length) {
|
||||
decls.push(''); // make sure we end with a space
|
||||
}
|
||||
return decls.join(' ');
|
||||
}
|
||||
|
||||
const TypeModifiers = {
|
||||
public: 0b0000_0000_0000_0001, // 0x1
|
||||
final: 0b0000_0000_0001_0000, // 0x10
|
||||
interface: 0b0000_0010_0000_0000, // 0x200
|
||||
abstract: 0b0000_0100_0000_0000, // 0x400
|
||||
'@interface': 0b0010_0000_0000_0000, // 0x2000
|
||||
enum: 0b0100_0000_0000_0000, // 0x4000
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} modifier_bits
|
||||
*/
|
||||
function typemods(modifier_bits) {
|
||||
const modifiers = [];
|
||||
let type = 'class';
|
||||
if (modifier_bits & TypeModifiers.interface) {
|
||||
type = 'interface';
|
||||
modifier_bits &= ~TypeModifiers.abstract; // ignore abstract keyword for interfaces
|
||||
} else if (modifier_bits & TypeModifiers['@interface']) {
|
||||
type = '@interface';
|
||||
} else if (modifier_bits & TypeModifiers.enum) {
|
||||
type = 'enum';
|
||||
modifier_bits &= ~TypeModifiers.final; // ignore final keyword for enums
|
||||
}
|
||||
if (modifier_bits & TypeModifiers.public) modifiers.push('public');
|
||||
if (modifier_bits & TypeModifiers.final) modifiers.push('final');
|
||||
if (modifier_bits & TypeModifiers.abstract) modifiers.push('abstract');
|
||||
modifiers.push(type);
|
||||
return modifiers.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* List of known/common packages.
|
||||
* These are used/encoded as pkgrefs between 0 and 15.
|
||||
*/
|
||||
const KnownPackages = ["java.lang","java.io","java.util",""];
|
||||
|
||||
/**
|
||||
* Literals corresponding to the KnownTypes.
|
||||
* These are used for method return values and field expressions when constructing source.
|
||||
*/
|
||||
const KnownTypeValues = ['','0','""','null','false',"'\\0'",'0','0l','0','0.0f','0.0d','null'];
|
||||
|
||||
/**
|
||||
* List of known/common types.
|
||||
* These are used/encoded as typerefs between 0 and 15.
|
||||
*/
|
||||
const KnownTypes = [
|
||||
"void","int","String","Object","boolean","char","byte","long","short","float","double","Class"
|
||||
].map((n,i) => {
|
||||
const pkg_or_prim = /^[SOC]/.test(n) ? KnownPackages[0] : false;
|
||||
return new ReferencedType(null, {n}, pkg_or_prim, KnownTypeValues[i]);
|
||||
});
|
||||
|
||||
module.exports = MTI;
|
||||
56
langserver/package-lock.json
generated
Normal file
56
langserver/package-lock.json
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "langserver",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==",
|
||||
"dev": true
|
||||
},
|
||||
"jarscanner": {
|
||||
"version": "file:../../../nodejs/jarscanner",
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "13.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz",
|
||||
"integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vscode-jsonrpc": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",
|
||||
"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
|
||||
},
|
||||
"vscode-languageserver": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz",
|
||||
"integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==",
|
||||
"requires": {
|
||||
"vscode-languageserver-protocol": "^3.15.3"
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-protocol": {
|
||||
"version": "3.15.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz",
|
||||
"integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==",
|
||||
"requires": {
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"vscode-languageserver-types": "3.15.1"
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-textdocument": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz",
|
||||
"integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA=="
|
||||
},
|
||||
"vscode-languageserver-types": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
|
||||
"integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
19
langserver/package.json
Normal file
19
langserver/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "langserver",
|
||||
"version": "1.0.0",
|
||||
"description": "Java language server for Android development",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"jarscanner": "file://~/dev/nodejs/jarscanner",
|
||||
"vscode-languageserver": "^6.1.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.13.4"
|
||||
}
|
||||
}
|
||||
337
langserver/server.js
Normal file
337
langserver/server.js
Normal file
@@ -0,0 +1,337 @@
|
||||
const fs = require('fs');
|
||||
const {
|
||||
createConnection,
|
||||
TextDocuments,
|
||||
//TextDocument,
|
||||
Diagnostic,
|
||||
DiagnosticSeverity,
|
||||
ProposedFeatures,
|
||||
//InitializeParams,
|
||||
DidChangeConfigurationNotification,
|
||||
CompletionItem,
|
||||
CompletionItemKind,
|
||||
TextDocumentSyncKind,
|
||||
//TextDocumentPositionParams
|
||||
} = require('vscode-languageserver');
|
||||
|
||||
const { TextDocument } = require('vscode-languageserver-textdocument');
|
||||
|
||||
const MTI = require('./java/mti');
|
||||
let androidLibrary = null;
|
||||
function loadAndroidLibrary(retry) {
|
||||
try {
|
||||
androidLibrary = MTI.unpackJSON('/tmp/jarscanner/android-25/android-25.json');
|
||||
connection.console.log(`Android type cache loaded: ${androidLibrary.types.length} types from ${androidLibrary.packages.length} packages.`);
|
||||
} catch (e) {
|
||||
connection.console.log(`Failed to load android type cache`);
|
||||
if (retry) {
|
||||
return;
|
||||
}
|
||||
connection.console.log(`Rebuilding type cache...`);
|
||||
const jarscanner = require(`jarscanner/jarscanner`);
|
||||
fs.mkdir('/tmp/jarscanner', err => {
|
||||
if (err) {
|
||||
connection.console.log(`Cannot create type cache folder. ${err.message}.`);
|
||||
return
|
||||
}
|
||||
jarscanner.process_android_sdk_source({
|
||||
destpath: '/tmp/jarscanner',
|
||||
sdkpath: process.env['ANDROID_SDK'],
|
||||
api: 25,
|
||||
cleandest: true,
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
connection.console.log(`Android cache build failed. ${err.message}.`);
|
||||
return
|
||||
}
|
||||
loadAndroidLibrary(true);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
let connection = createConnection(ProposedFeatures.all);
|
||||
|
||||
// Create a simple text document manager. The text document manager
|
||||
// supports full document sync only
|
||||
let documents = new TextDocuments({
|
||||
/**
|
||||
*
|
||||
* @param {string} uri
|
||||
* @param {string} languageId
|
||||
* @param {number} version
|
||||
* @param {string} content
|
||||
*/
|
||||
create(uri, languageId, version, content) {
|
||||
connection.console.log(JSON.stringify({what:'create',uri,languageId,version,content}));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @param {*} document
|
||||
* @param {import('vscode-languageserver').TextDocumentContentChangeEvent[]} changes
|
||||
* @param {number} version
|
||||
*/
|
||||
update(document, changes, version) {
|
||||
connection.console.log(JSON.stringify({what:'update',changes,version}));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let hasConfigurationCapability = false;
|
||||
let hasWorkspaceFolderCapability = false;
|
||||
let hasDiagnosticRelatedInformationCapability = false;
|
||||
|
||||
connection.onInitialize((params) => {
|
||||
process.nextTick(loadAndroidLibrary);
|
||||
let capabilities = params.capabilities;
|
||||
|
||||
// Does the client support the `workspace/configuration` request?
|
||||
// If not, we will fall back using global settings
|
||||
hasConfigurationCapability =
|
||||
capabilities.workspace && !!capabilities.workspace.configuration;
|
||||
|
||||
hasWorkspaceFolderCapability =
|
||||
capabilities.workspace && !!capabilities.workspace.workspaceFolders;
|
||||
|
||||
hasDiagnosticRelatedInformationCapability =
|
||||
capabilities.textDocument &&
|
||||
capabilities.textDocument.publishDiagnostics &&
|
||||
capabilities.textDocument.publishDiagnostics.relatedInformation;
|
||||
|
||||
return {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
// Tell the client that the server supports code completion
|
||||
completionProvider: {
|
||||
resolveProvider: true
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
connection.onInitialized(() => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Register for all configuration changes.
|
||||
connection.client.register(DidChangeConfigurationNotification.type, undefined);
|
||||
}
|
||||
if (hasWorkspaceFolderCapability) {
|
||||
connection.workspace.onDidChangeWorkspaceFolders(_event => {
|
||||
connection.console.log('Workspace folder change event received.');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// The example settings
|
||||
/**
|
||||
* @typedef ExampleSettings
|
||||
* @property {number} maxNumberOfProblems
|
||||
*/
|
||||
|
||||
// The global settings, used when the `workspace/configuration` request is not supported by the client.
|
||||
// Please note that this is not the case when using this server with the client provided in this example
|
||||
// but could happen with other clients.
|
||||
const defaultSettings = { maxNumberOfProblems: 1000 };
|
||||
let globalSettings = defaultSettings;
|
||||
|
||||
// Cache the settings of all open documents
|
||||
/** @type {Map<string, Thenable<ExampleSettings>>} */
|
||||
let documentSettings = new Map();
|
||||
|
||||
connection.onDidChangeConfiguration(change => {
|
||||
if (hasConfigurationCapability) {
|
||||
// Reset all cached document settings
|
||||
documentSettings.clear();
|
||||
} else {
|
||||
globalSettings = (
|
||||
(change.settings.androidJavaLanguageServer || defaultSettings)
|
||||
);
|
||||
}
|
||||
|
||||
// Revalidate all open text documents
|
||||
documents.all().forEach(validateTextDocument);
|
||||
});
|
||||
|
||||
function getDocumentSettings(resource) {
|
||||
if (!hasConfigurationCapability) {
|
||||
return Promise.resolve(globalSettings);
|
||||
}
|
||||
let result = documentSettings.get(resource);
|
||||
if (!result) {
|
||||
result = connection.workspace.getConfiguration({
|
||||
scopeUri: resource,
|
||||
section: 'androidJavaLanguageServer'
|
||||
});
|
||||
documentSettings.set(resource, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Only keep settings for open documents
|
||||
documents.onDidClose(e => {
|
||||
documentSettings.delete(e.document.uri);
|
||||
});
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
// documents.onDidChangeContent(change => {
|
||||
// connection.console.log(JSON.stringify(change));
|
||||
//validateTextDocument(change.document);
|
||||
// });
|
||||
|
||||
/**
|
||||
* @param {TextDocument} textDocument
|
||||
*/
|
||||
async function validateTextDocument(textDocument) {
|
||||
// In this simple example we get the settings for every validate run.
|
||||
//let settings = await getDocumentSettings(textDocument.uri);
|
||||
|
||||
// The validator creates diagnostics for all uppercase words length 2 and more
|
||||
let text = textDocument.getText();
|
||||
let pattern = /\b[A-Z]{2,}\b/g;
|
||||
let m;
|
||||
|
||||
let problems = 0;
|
||||
let diagnostics = [];
|
||||
while ((m = pattern.exec(text)) /* && problems < settings.maxNumberOfProblems */) {
|
||||
problems++;
|
||||
/** @type {Diagnostic} */
|
||||
let diagnostic = {
|
||||
severity: DiagnosticSeverity.Warning,
|
||||
range: {
|
||||
start: textDocument.positionAt(m.index),
|
||||
end: textDocument.positionAt(m.index + m[0].length)
|
||||
},
|
||||
message: `${m[0]} is all uppercase.`,
|
||||
source: 'ex'
|
||||
};
|
||||
if (hasDiagnosticRelatedInformationCapability) {
|
||||
diagnostic.relatedInformation = [
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range)
|
||||
},
|
||||
message: 'Spelling matters'
|
||||
},
|
||||
{
|
||||
location: {
|
||||
uri: textDocument.uri,
|
||||
range: Object.assign({}, diagnostic.range)
|
||||
},
|
||||
message: 'Particularly for names'
|
||||
}
|
||||
];
|
||||
}
|
||||
diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
// Send the computed diagnostics to VS Code.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
|
||||
connection.onDidChangeWatchedFiles(_change => {
|
||||
// Monitored files have change in VS Code
|
||||
connection.console.log('We received a file change event');
|
||||
});
|
||||
|
||||
// This handler provides the initial list of the completion items.
|
||||
let allCompletionTypes = null;
|
||||
connection.onCompletion(
|
||||
/**
|
||||
* @param {*} _textDocumentPosition TextDocumentPositionParams
|
||||
*/
|
||||
(_textDocumentPosition) => {
|
||||
// The pass parameter contains the position of the text document in
|
||||
// which code complete got requested. For the example we ignore this
|
||||
// info and always provide the same completion items.
|
||||
const lib = androidLibrary;
|
||||
if (!lib) return [];
|
||||
const typeKindMap = {
|
||||
'class':CompletionItemKind.Class,
|
||||
'interface': CompletionItemKind.Interface,
|
||||
'@interface': CompletionItemKind.Interface,
|
||||
'enum': CompletionItemKind.Enum,
|
||||
};
|
||||
return allCompletionTypes || (allCompletionTypes = lib.types.map((t,idx) =>
|
||||
/** @type {CompletionItem} */
|
||||
({
|
||||
label: t.dottedRawName,
|
||||
kind: typeKindMap[t.typeKind],
|
||||
data: idx
|
||||
})
|
||||
));
|
||||
return [
|
||||
{
|
||||
label: 'TypeScript',
|
||||
kind: CompletionItemKind.Text,
|
||||
data: 1
|
||||
},
|
||||
{
|
||||
label: 'JavaScript',
|
||||
kind: CompletionItemKind.Text,
|
||||
data: 2
|
||||
}
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
// This handler resolves additional information for the item selected in
|
||||
// the completion list.
|
||||
connection.onCompletionResolve(
|
||||
/**
|
||||
* @param {CompletionItem} item
|
||||
*/
|
||||
(item) => {
|
||||
const t = androidLibrary.types[item.data];
|
||||
item.detail = `${t.package}.${t.dottedRawName}`;
|
||||
item.documentation = t.docs && {
|
||||
kind: "markdown",
|
||||
value: `${t.typeKind} **${t.dottedName}**\n\n${
|
||||
t.docs
|
||||
.replace(/(<p ?.*?>)|(<\/?i>|<\/?em>)|(<\/?b>|<\/?strong>|<\/?dt>)|(<\/?tt>)|(<\/?code>|<\/?pre>)|(\{@link.+?\}|\{@code.+?\})|(<li>)|(<a href="\{@docRoot\}.*?">.+?<\/a>)|(<h\d>)|<\/?dd ?.*?>|<\/p ?.*?>|<\/h\d ?.*?>|<\/?div ?.*?>|<\/?[uo]l ?.*?>/gim, (_,p,i,b,tt,c,lc,li,a,h) => {
|
||||
return p ? '\n\n'
|
||||
: i ? '*'
|
||||
: b ? '**'
|
||||
: tt ? '`'
|
||||
: c ? '\n```'
|
||||
: lc ? lc.replace(/\{@\w+\s*(.+)\}/, (_,x) => `\`${x.trim()}\``)
|
||||
: li ? '\n- '
|
||||
: a ? a.replace(/.+?\{@docRoot\}(.*?)">(.+?)<\/a>/m, (_,p,t) => `[${t}](https://developer.android.com/${p})`)
|
||||
: h ? `\n${'#'.repeat(1 + parseInt(h.slice(2,-1),10))} `
|
||||
: '';
|
||||
})
|
||||
}`,
|
||||
}
|
||||
return item;
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
connection.onDidOpenTextDocument((params) => {
|
||||
// A text document got opened in VS Code.
|
||||
// params.uri uniquely identifies the document. For documents store on disk this is a file URI.
|
||||
// params.text the initial full content of the document.
|
||||
connection.console.log(`${params.textDocument.uri} opened.`);
|
||||
});
|
||||
connection.onDidChangeTextDocument((params) => {
|
||||
// The content of a text document did change in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
// params.contentChanges describe the content changes to the document.
|
||||
connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
|
||||
});
|
||||
connection.onDidCloseTextDocument((params) => {
|
||||
// A text document got closed in VS Code.
|
||||
// params.uri uniquely identifies the document.
|
||||
connection.console.log(`${params.textDocument.uri} closed.`);
|
||||
});
|
||||
*/
|
||||
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
|
||||
35
package-lock.json
generated
35
package-lock.json
generated
@@ -1232,6 +1232,41 @@
|
||||
"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.40.0.tgz",
|
||||
"integrity": "sha512-Fwze+9qbLDPuQUhtITJSu/Vk6zIuakNM1iR2ZiZRgRaMEgBpMs2JSKaT0chrhJHCOy6/UbpsUbUBIseF6msV+g=="
|
||||
},
|
||||
"vscode-jsonrpc": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",
|
||||
"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A=="
|
||||
},
|
||||
"vscode-languageclient": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz",
|
||||
"integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==",
|
||||
"requires": {
|
||||
"semver": "^6.3.0",
|
||||
"vscode-languageserver-protocol": "^3.15.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-protocol": {
|
||||
"version": "3.15.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz",
|
||||
"integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==",
|
||||
"requires": {
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"vscode-languageserver-types": "3.15.1"
|
||||
}
|
||||
},
|
||||
"vscode-languageserver-types": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
|
||||
"integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ=="
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
|
||||
16
package.json
16
package.json
@@ -20,7 +20,8 @@
|
||||
"activationEvents": [
|
||||
"onCommand:android-dev-ext.view_logcat",
|
||||
"onCommand:PickAndroidDevice",
|
||||
"onCommand:PickAndroidProcess"
|
||||
"onCommand:PickAndroidProcess",
|
||||
"onLanguage:java"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,6 +29,18 @@
|
||||
},
|
||||
"main": "./extension",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Java (Android)",
|
||||
"properties": {
|
||||
"androidJavaLanguageServer.maxNumberOfProblems": {
|
||||
"scope": "resource",
|
||||
"type": "number",
|
||||
"default": 100,
|
||||
"description": "Controls the maximum number of problems produced by the server."
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "android-dev-ext.view_logcat",
|
||||
@@ -228,6 +241,7 @@
|
||||
"uuid": "^3.3.2",
|
||||
"vscode-debugadapter": "^1.40.0",
|
||||
"vscode-debugprotocol": "^1.40.0",
|
||||
"vscode-languageclient": "6.1.3",
|
||||
"ws": "^7.1.2",
|
||||
"xmldom": "^0.1.27",
|
||||
"xpath": "^0.0.27"
|
||||
|
||||
Reference in New Issue
Block a user